I have test-driven a Backbone model in Javascript, so I'm sure that when user clicks the "Save" button, a proper POST request is being sent to my ASP.NET MVC application. This is the final integration-like test for this model (this.server
is a Fake server from Sinon.JS):
it('should properly formulate request to save data', function () {
this.model.data = [{ id: 1, type: 'type', value: 'value' }];
this.model.save();
expect(this.server.requests.length).toEqual(1);
expect(this.server.requests[0].method).toEqual('POST');
expect(this.server.requests[0].url).toEqual('MyController/SaveData');
expect(this.server.requests[0].requestHeaders['Content-Type'])
.toEqual('application/json;charset=utf-8');
expect(this.server.requests[0].requestBody)
.toEqual('[{"id":1,"type":"type","value":"value"}]');
});
Now I want to test-drive the controller. I want to be sure that I've not only properly implemented the SaveData
action (that's easy), but I also want to verify that the mapping from request body to action arguments and MVC routes make sense.
I've found many questions regarding unit-testing controllers with stubbed HttpContextBase
et consortes, for example:
- ASP.NET MVC unit test controller with HttpContext
- How to mock the Request on Controller in ASP.Net MVC?
Unfortunately, they all instantiate the controller and call the action method manually. This is unsatisfactory for me: I want to assert that the same request content that's going out from JS (and is guarded by the aforementioned unit-test) is what will correctly work on the ASP.NET application side.
What I currently have is just a draft to make it work and illustrate the problem. I'm using Rhino Mocks for stubs and mocks. In particular, dataWebService
is a mock I want to use for assertions. I've included it just to make clear what's the point of the test, but in general it's of course irrelevant to the problem. The problem is twofold (controller instantiation and action invocation) and is indicated by the comments in the following code:
[Test]
public void GivenNoData_WhenPostingData_ThenCallsWebServiceSaveData()
{
var httpContext = MockRepository.GenerateStub<HttpContextBase>();
var httpRequest = MockRepository.GenerateStub<HttpRequestBase>();
httpRequest
.Stub(hr => hr.Url)
.Return(new Uri("http://localhost/MyController/SaveData"));
httpRequest
.Stub(hr => hr.Headers)
.Return(new WebHeaderCollection()
{
{ "X-Requested-With", "XMLHttpRequest" },
{ "Content-Type", "application/json;charset=utf-8" }
});
httpRequest
.Stub(hr => hr.RequestType)
.Return("POST");
var requestBody = @"[{""id"":1,""type"":""type"",""value"":""value""}]";
httpRequest
.Stub(hr => hr.InputStream)
.Return(new MemoryStream(Encoding.UTF8.GetBytes(requestBody)));
httpContext.Stub(hc => hc.Request).Return(httpRequest);
// The problem starts here
// I want MVC to instantiate the controller based on the request
var controller = new MyController(dataWebService);
controller.ControllerContext
= new ControllerContext(httpContext, new System.Web.Routing.RouteData(), controller);
dataWebService.Expect(dws => dws.SaveData(Arg<Data>.Matches(/*...*/));
// Second part of the problem, I want MVC to invoke SaveData with arguments
// generated from request's body
controller.SaveData(/* arguments */);
dataWebService.VerifyAllExpectations();
}
Now, I know that this doesn't match a strict definition of a unit-test and is somewhere between unit-testing and integration testing.
However, first I want to have confidence that the whole process, from top to bottom, is covered by tests, and then I'll worry about definitions (and perhaps split the test into a unit-test for controller only and an integration-like test for routing and controller arguments parsing).
Also note, that assuming MVC works correctly, the point is to test only my own code, in particular SaveData
method signature and MVC route configuration.