Introduction
Download WcfUnity.zip (VS2013)
I have removed all the NuGet packages and the bin dll's from the zipped download for compactability reasons. But, when you compile the project - the solution will download all the respective assemblies from NuGet. I am using Visual Studio 2013, but you can use VS2012 Web Express upwards.
Background
When unit testing your controller method, you want to mock out the layers that your code communicates with. This can be easily done using mocking frameworks like RhinoMock or MOQ. It's always the first and last layer that cannot be mocked, as you do not have mocking control over the UI (first layer) or the external service or database procedure (last layer), for example. For these scenarios, you will use integration testing tools like Selenium or CodedUI to perfrom that end-2-end testing (UI through to service\database).
But, there are scenarios when you second last layer needs to be mocked out, but your control over it again is limited, as it maybe deployed onto a server and your application is consuming it - for example a soap services that communicates with and external database or performs some number crunching logic. In your unit test you are only concerned with the logic inside the controller method not the service - thus, you would like to mock the service call, as the service will have already been unit tested before it's deployment.
IOC & Project Structure
For my IOC container, I am going to be using the MVC.Unity 4 Nuget package. My test project is split up into three projects:
- WCF service project
- MVC web application
- Test project
Code Explanation
For simplicity sake, I have setup up the Unity Resolver within the Application_Start method within the Global.asax class (a better approach is to create a static BootStraper method and call that from Application_Start - making the code more maintainable - as this method, overtime in a normal application will become code congested).
Before I add the Unity Resolver code, you will want to consume your externall service into your project. In my case I deployed StockServices
to my local IIS and then consumed the WSDL into my project as normal.
protected void Application_Start()
{
var container = new UnityContainer();
container
.RegisterType<IStock, StockClient>()
.Configure<InjectedMembers>()
.ConfigureInjectionFor<StockClient>(new InjectionConstructor("*"));
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
The snippet of code above is very standard for resolving an interface to a concrete class. The only thing of interest is where do I get the service name - StockCleint
from?
If you double click on your service refernce within your project, the Object Browser window will open and you can scroll down to the service entry - the service name will always have a postfix of Client
. This is the concrete class name you want your service interface to resolve to.
Now that you know where and how-to resolve your service dependencies. the next section is how do I inject the service into my Controllers, and then execute the service methods after Constructor injection.
Controller Code
In the snippet of code below, there are a couple lines of code that we are interested in. Namely, the constructor injection of our interface IStock
and the private variable stock
. Only from MVC 3 has it been possible to create a controller class with a constructor. Thus, we can use Unity's Constructor Dependency Injection approach, to implement our concrete class through the constructor.
Injecting a service into your controller is now very easy and making the service method calls is also straight forward - but the important thing to note is that you are dependency injecting your interface, and thus you will be able to mock your interface at a later date in your tests. For simplistic reasons, I am just calling the service to bring back a data type and a class structure.
public class HomeController : Controller
{
private readonly StockService.IStock stock;
public HomeController(StockService.IStock stk)
{
stock = stk;
}
public ActionResult Index()
{
ViewBag.Message = stock.GetData(10);
return View("Index");
}
public ActionResult About()
{
StockService.StockData data = stock.GetStockData();
ViewBag.Message = String.Format("Your stock {0} has a value {1}.", data.StockName, data.StockValue);
return View("About", data);
}
}
Creating Tests
The hard part is already done, as we simply create our unit tests as normal, mocking out the service and it's methods. We inject the mock service into the controller constructor that we are looking to test, then test the controller method as normal. In the tests below I simply test the mocked Model or ViewBag, but you might want to use MVC helpers to test the html being generated etc.
[TestClass]
public class HomeControllerTest
{
[TestMethod]
public void Index()
{
string expectedViewName = "Index";
string returnValue = "300";
Mock<IStock> mockIStock = new Mock<IStock>();
mockIStock.Setup(t => t.GetData(It.IsAny<int>())).Returns(returnValue);
HomeController controller = new HomeController(mockIStock.Object);
ViewResult result = controller.Index() as ViewResult;
Assert.IsNotNull(result);
Assert.AreEqual(result.ViewBag.message, returnValue);
Assert.AreEqual(expectedViewName, result.ViewName, "View name should have been {0}", expectedViewName);
}
[TestMethod]
public void About()
{
string expectedViewName = "About";
string stockName = "BON";
double stockValue = 1;
Mock<StockData> mockStockData = new Mock<StockData>();
mockStockData.Object.StockName = stockName;
mockStockData.Object.StockValue = stockValue;
Mock<IStock> mockIStock = new Mock<IStock>();
mockIStock.Setup(t => t.GetStockData()).Returns(mockStockData.Object);
HomeController controller = new HomeController(mockIStock.Object);
ViewResult result = controller.About() as ViewResult;
Assert.IsNotNull(result);
Assert.IsTrue(result.ViewBag.message == String.Format("Your stock {0} has a value {1}.", stockName, stockValue));
Assert.AreEqual(expectedViewName, result.ViewName, "View name should have been {0}", expectedViewName);
Assert.AreEqual(result.Model, mockStockData.Object, "Model should have been {0} and {1}", mockStockData.Object.StockName, mockStockData.Object.StockValue);
}
}
Running Tests
To run the tests within VS2012\13, simply right click your test class and select Run Tests from the context menu, this will then display the results of your tests - you can insert a breakpoint and debug as normal through your tests.
Conclusion
Mocking external service is extremely easy, there is a little extra step when working with dynamically built up service endpoints - but again you would just use the endpoint details (for example, WSHttpBinding_IStock
) as your resolving concrete class when lazy loading a service.