Now that we can batch WCF calls, and do so with readable code we still need a way to test this.
Suppose that we have a page where we need to display a dropdown list of product categories and suppliers. This is the code in the controller of that screen for the Load event:
if (!View.IsPostBack)
{
var batcher = new ServiceCallBatcher(service);
batcher.Add("categories", new GetProductCategoriesRequest());
batcher.Add("suppliers", new GetSuppliersRequest());
View.ProductCategories = batcher.Get<GetProductCategoriesResponse>("categories").ProductCategories;
View.Suppliers = batcher.Get<GetSuppliersResponse>("suppliers").Suppliers;
View.DataBind();
}
When i'm testing my controllers, i like to use mocked service instances, and i set expectations on the methods that should be called and the parameters they receive. With my batching technique, i don't really execute specific methods on the service because the batcher calls this method:
Response[] Process(params Request[] requests);
My Request classes are really simple, and to keep them simple they don't override the Equals method. This makes it hard to set expectations because the Request instances are created by the controller, and i can't set expectations on the Process method using Request instances that would equal the Request instances that were passed to the batcher by the controller. This makes it hard to test that the service is called correctly. If a Request type contains properties (which are really service method parameters) you really want to be able to test that those properties contain the correct values. Also, you want to make sure that the correct Requests are sent to the service. But if they're handled by this very generic Process method, it makes it hard to verify correct usage during a test.
So how can we properly test the code listed above? I came up with the following approach. First we need a class that we can use to set up the Response instances to return from the service's Process method, and to capture the Request instances that are passed to the Process method:
public class ServiceRequestResponseSpy
{
private Request[] receivedRequests;
private Response[] responsesToReturn;
public void SetResponsesToReturn(params Response[] responses)
{
responsesToReturn = responses;
}
public T GetRequest<T>(int index) where T : Request
{
return (T)receivedRequests[index];
}
public Response[] GrabRequestsAndReturnGivenResponses(params Request[] requests)
{
receivedRequests = requests;
return responsesToReturn;
}
}
Now we can write our test like this:
[Test]
public void RetrievesCategoriesAndSuppliersOnLoadIfNotPostBack()
{
view.Stub(v => v.IsPostBack).Return(false);
var categoriesToReturn = new ProductCategoryDTO[0];
var suppliersToReturn = new SupplierDTO[0];
var spy = new ServiceRequestResponseSpy();
spy.SetResponsesToReturn(new GetProductCategoriesResponse(categoriesToReturn),
new GetSuppliersResponse(suppliersToReturn));
service.Stub(s => s.Process(null))
.IgnoreArguments()
.Do(new Func<Request[], Response[]>(spy.GrabRequestsAndReturnGivenResponses));
view.Expect(v => v.ProductCategories = categoriesToReturn);
view.Expect(v => v.Suppliers = suppliersToReturn);
view.Expect(v => v.DataBind());
CreateController();
controller.Load();
view.VerifyAllExpectations();
Assert.IsNotNull(spy.GetRequest<GetProductCategoriesRequest>(0));
Assert.IsNotNull(spy.GetRequest<GetSuppliersRequest>(1));
}
The important part of the code is this one:
service.Stub(s => s.Process(null))
.IgnoreArguments()
.Do(new Func<Request[], Response[]>(spy.GrabRequestsAndReturnGivenResponses));
This basically tells Rhino Mocks to execute the spy's GrabRequestsAndReturnGivenRepsonses method whenever the mocked service instance's Process method is called, no matter what arguments are passed to it.
So we basically set up the mocked service to delegate the call to Process to the GrabRequestsAndReturnGivenRepsonses method, and then we set expectations on how the returned data should be passed to the view. After that we execute the Controller's Load method, and we verify that the view's expectations were met, and we test that the spy captured Request instances of the expected types.