Reputation: 35891
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:
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.
Upvotes: 1
Views: 1523
Reputation: 35891
jtabuloc has correctly pointed out that this is definitely an integration test issue. The requirement about having ASP.NET MVC stack running, and at the same time have an internal WebService stubbed is very hard to achieve, even if assuming that has some sense.
From what I've learned, the only option to provide an encapsulated environment (i.e. no dedicated server) for integration testing a Web Application is to use either OWIN or ASP.NET Core. Currently, none of these are viable for me, as require upgrading MVC 4 and MVC 5 applications.
Therefore this doesn't seem to be worth the cost currently - I'll stick to integration testing for WebService with a database, and unit-testing controllers and views.
Upvotes: 0
Reputation: 2535
This sounds an integration test to me. Anyways, forget RhinoMock in this case because there's no other way to do it but to create your own test suite here. In our case we actually used HttpClient to call the controller/api and pass the url and argument needed for action and anticipate the result for validation.
public class ClientHandler
{
private HttpClient _client;
public ClientHander()
{
// add handler if needed ex var handler = new HttpClientHandler()
_client = new HttpClient(/*handler*/);
// add default header if needed client.DefaultRequestHeaders
}
public HttpResponseMessage Post(string path, HttpContent content)
{
// You can async if you want
return _client.Post(path, content).Result;
}
}
Now you can use it in your actual testing.
[TestClass]
public class MyController
{
[TestMethod]
public void TestMyControllerActionSaveData()
{
var client = new ClientHandler();
var content = new StringContent(dataHere, Encoding.UTF8, "application/json");
var outPut = client.Post("http://server/action/here", content).Result;
// validate expected output here
}
}
There are a lot of missing configuration and setup here but the gist is there.
Update : I really like what you are currently doing in the name of testing because this is a powerful tool as part of Continues Integration (CI). Just a minor comment the way to name your test method. I would suggest to rename the test method into an action you want to do and put those procedure inside the test like what Gherkin way of BDD or as described by Dan North .
[TestMethod]
public void Should_Save_If_Has_Data()
{
Given(Web_Service_Instance)
With(Data_To_Pass)
When(Posting_Data_To_Service)
Then(Data_Will_Be_Saved)
Verify()
}
[TestMethod]
public void Should_Not_Save_If_No_Data()
{
.....
}
If you can create a TestSuite like described above it will give you a lot of benefits in the long run and avoid repetition of code (DRY). Also those tests will serve as living documents as described by Robert C. Martin & Micah Martin. Special thanks to the team that Im involved and kudos is for them!
Upvotes: 1