模拟 Asp.net-mvc 控制器上下文

70

所以控制器上下文依赖于一些asp.net内部机制。有什么方法可以干净地模拟这些单元测试?似乎很容易在测试中堆积大量设置,而我只需要比如说,让请求的HttpMethod返回“GET”。

我看到一些网络上的示例/助手,但有些已经过时了。想知道最新、最好的方法。

我正在使用rhino mocks的最新版本。


我曾考虑过这样做。但是只需要数据库连接的模拟。我将功能移动到普通类中,仅测试该函数,而不进行数据库映射测试。 - MrFox
7个回答

66

使用 MoQ 看起来像这样:

var request = new Mock<HttpRequestBase>();
request.Expect(r => r.HttpMethod).Returns("GET");
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Expect(c => c.Request).Returns(request.Object);
var controllerContext = new ControllerContext(mockHttpContext.Object
, new RouteData(), new Mock<ControllerBase>().Object);

我认为Rhino Mocks的语法很相似。


17
在MVC3中,这不再有效。传入空的RouteData将在调用RouteData上的非虚拟、非可模拟方法GetRequiredString时抛出异常。 翻译:在MVC3中,传入一个空的RouteData会导致在调用RouteData上的GetRequiredString方法时抛出异常,该方法是非虚拟的且不可被模拟。 - ScottKoon
3
@ScottKoon,请提供一个演示,展示它应该是什么样子。 - Jon
1
这个问题的答案告诉您如何模拟RouteData。 https://dev59.com/HHNA5IYBdhLWcg3wX8vk - ScottKoon

23
以下是使用MsTest和Moq的示例单元测试类,它模拟了HttpRequest和HttpResponse对象。(.NET 4.0,ASP.NET MVC 3.0)
控制器操作从请求中获取值,并在响应对象中设置http头。其他http上下文对象可以用类似的方式进行模拟。
[TestClass]
public class MyControllerTest
{
    protected Mock<HttpContextBase> HttpContextBaseMock;
    protected Mock<HttpRequestBase> HttpRequestMock;
    protected Mock<HttpResponseBase> HttpResponseMock;

    [TestInitialize]
    public void TestInitialize()
    {
        HttpContextBaseMock = new Mock<HttpContextBase>();
        HttpRequestMock = new Mock<HttpRequestBase>();
        HttpResponseMock = new Mock<HttpResponseBase>();
        HttpContextBaseMock.SetupGet(x => x.Request).Returns(HttpRequestMock.Object);
        HttpContextBaseMock.SetupGet(x => x.Response).Returns(HttpResponseMock.Object);
    }

    protected MyController SetupController()
    {
        var routes = new RouteCollection();
        var controller = new MyController();
        controller.ControllerContext = new ControllerContext(HttpContextBaseMock.Object, new RouteData(), controller);
        controller.Url = new UrlHelper(new RequestContext(HttpContextBaseMock.Object, new RouteData()), routes);
        return controller;
    }

    [TestMethod]
    public void IndexTest()
    {
        HttpRequestMock.Setup(x => x["x"]).Returns("1");
        HttpResponseMock.Setup(x => x.AddHeader("name", "value"));

        var controller = SetupController();
        var result = controller.Index();
        Assert.AreEqual("1", result.Content);

        HttpRequestMock.VerifyAll();
        HttpResponseMock.VerifyAll();
    }
}

public class MyController : Controller
{
    public ContentResult Index()
    {
        var x = Request["x"];
        Response.AddHeader("name", "value");
        return Content(x);
    }
}

2
非常感谢!它对我帮助很大。我希望我能给它点赞十次。 - Maxim Eliseev

19

这是来自Jason链接的一段代码片段。它和Phil的方法相同,但使用Rhino。

注意:在对mockRequest的内部进行存根之前,mockHttpContext.Request被桩占位返回。我认为这种顺序是必需的。

// create a fake web context
var mockHttpContext = MockRepository.GenerateMock<HttpContextBase>();
var mockRequest = MockRepository.GenerateMock<HttpRequestBase>();
mockHttpContext.Stub(x => x.Request).Return(mockRequest);

// tell the mock to return "GET" when HttpMethod is called
mockRequest.Stub(x => x.HttpMethod).Return("GET");            

var controller = new AccountController();

// assign the fake context
var context = new ControllerContext(mockHttpContext, 
                  new RouteData(), 
                  controller);
controller.ControllerContext = context;

// act
...

10
这个过程在MVC2中似乎有些变化(我正在使用RC1)。Phil Haack的解决方法对我不起作用,如果操作需要特定的方法([HttpPost][HttpGet])。在Reflector中挖掘,看起来验证这些属性的方法已经改变。现在,MVC会检查request.Headersrequest.Formrequest.QueryString是否有X-HTTP-Method-Override值。
如果您为这些属性添加模拟,则可以正常工作:
var request = new Mock<HttpRequestBase>();
request.Setup(r => r.HttpMethod).Returns("POST");
request.Setup(r => r.Headers).Returns(new NameValueCollection());
request.Setup(r => r.Form).Returns(new NameValueCollection());
request.Setup(r => r.QueryString).Returns(new NameValueCollection());

var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Expect(c => c.Request).Returns(request.Object);
var controllerContext = new ControllerContext(mockHttpContext.Object, new RouteData(), new Mock<ControllerBase>().Object);

1
这对我有用,但在MVC2 RC中,我还必须添加以下内容: request.Setup(r => r.Files).Returns(new Mock<HttpFileCollectionBase>().Object);否则我会得到一个nullreferenceexception。 - Hugo Zapata

6

您可以使用Typemock Isolator来完成此操作,而无需发送任何虚拟控制器:

Isolate.WhenCalled(()=>HttpContext.Request.HttpMethod).WillReturn("Get");

2

我已经完成了这个规格说明书。



public abstract class Specification <C> where C: Controller
{
    protected C controller;

    HttpContextBase mockHttpContext;
    HttpRequestBase mockRequest;

    protected Exception ExceptionThrown { get; private set; }

    [SetUp]
    public void Setup()
    {
        mockHttpContext = MockRepository.GenerateMock<HttpContextBase>();
        mockRequest = MockRepository.GenerateMock<HttpRequestBase>();

        mockHttpContext.Stub(x => x.Request).Return(mockRequest);
        mockRequest.Stub(x => x.HttpMethod).Return("GET");


        EstablishContext();
        SetHttpContext();

        try
        {
            When();
        }
        catch (Exception exc)
        {
            ExceptionThrown = exc;
        }
    }

    protected void SetHttpContext()
    {
        var context = new ControllerContext(mockHttpContext, new RouteData(), controller);
        controller.ControllerContext = context;
    }

    protected T Mock<T>() where T: class
    {
        return MockRepository.GenerateMock<T>();
    }

    protected abstract void EstablishContext();
    protected abstract void When();

    [TearDown]
    public virtual void TearDown()
    {
    }
} 

果汁在这里

[TestFixture]
public class When_invoking_ManageUsersControllers_Update :Specification   <ManageUsersController>
{
    private IUserRepository userRepository;
    FormCollection form;

    ActionResult result;
    User retUser;

    protected override void EstablishContext()
    {
        userRepository = Mock<IUserRepository>();
        controller = new ManageUsersController(userRepository);

        retUser = new User();
        userRepository.Expect(x => x.GetById(5)).Return(retUser);
        userRepository.Expect(x => x.Update(retUser));

        form = new FormCollection();
        form["IdUser"] = 5.ToString();
        form["Name"] = 5.ToString();
        form["Surename"] = 5.ToString();
        form["Login"] = 5.ToString();
        form["Password"] = 5.ToString();
    }

    protected override void When()
    {
        result = controller.Edit(5, form);
    }

    [Test]
    public void is_retrieved_before_update_original_user()
    {
        userRepository.AssertWasCalled(x => x.GetById(5));
        userRepository.AssertWasCalled(x => x.Update(retUser));
    }
}

enjoy


0

我发现那个冗长的嘲弄过程太过繁琐。

我们在一个真实项目中使用ASP.NET MVC时,找到了最好的方法——将HttpContext抽象为一个简单的IWebContext接口进行传递。然后,您可以轻松地模拟IWebContext而不会感到疼痛。

这里是一个示例


2
你能再解释一下吗? - user40097

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接