Introduction
A few years ago, I got an opportunity to work on a project, which needed a migration from Web form to MVC and I was assigned a task to come up with a good testing approach to test the site. Afterwards, I worked with various companies, but I found an approach and I helped them to simplify the unit testing, using the same approach. It may be helpful to you also, as I would like to know what you think about it.
The example code is in very simple form and you can improvise it as, per your requirement. it is available at https://github.com/vicharemakrand/SampleCode-Testing
Background
You should have knowledge of the following.
- MOQ
- IOC – e.g. Structure map is used in the example.
- Unit Testing Framework – e.g. MS unit / Nunit / Xunit.
- Nbuilder is for generating mock data.
Take Away
- Using IOC to create mock objects.
- Maintain the test cases with less efforts.
- Helps to maintain uniformity in the code, which is very useful in scrum based projects.
Drawbacks of Traditional approach
- Lots of repeated code, where we mock the dependencies.
- Need efforts to keep the test cases up to date.
- It may lead to losing focus on testing business cases properly.
About the approach
Instead of mocking each method for each test and deciding the output, we will be using in- memory databases, which will be generated, using Nbuilder and the dependencies will be handled by an IOC.
Also, we don’t use mock Service layer method, using the MOQ but we use all the methods, as it is to call repository layer, which is mocked to use in the memory database.
Let’s start from the bottom up. The sample code covers User table, which is available on GitHub at https://github.com/vicharemakrand/SampleCode-Testing
Code Explanation
- In-memory mock database
We need mock data for each entity model, which is mapped to a database table.
- Create some mock records for the user table.
- namespace SampleCode.IOC.Test.MockTestData {
- public class UserModelTestData {
- public static List < UserModel > GetTestRecords() {
- return Builder < UserModel > .CreateListOfSize(10).Build().ToList();
- }
- }
- }
- Create a class to store the mock records in a collection
- public static class MockEntities {
- public static Dictionary < string, dynamic > MockData = new Dictionary < string, dynamic > ();
- public static void LoadData() {
- MockData.Add(typeof(UserModel).Name, UserModelTestData.GetTestRecords());
- }
- public static List < T > GetData < T > () where T: class {
- return (List < T > ) MockData[typeof(T).Name];
- }
- }
- Create a mock repository object using MOQ
Here, we set up repository methods but instead of hardcoding the sresult, we do actual simple operation related to the methods. This approach is the same with lots of duplicate code and maintenance efforts.
- namespace SampleCode.IOC.Test {
- public static class MockRepositoryGenerator < Model > where Model: BaseModel {
- private static List < Model > DummyTable {
- get {
- return MockEntities.GetData < Model > ();
- }
- }
- public static T RepositoryMock < T > () where T: class, IBaseRepository < Model > {
- Mock < T > repository = new Mock < T > (MockBehavior.Strict);
- repository.Setup(o => o.Get(It.IsAny < Expression < Func < Model, bool >>> ())).Returns((Expression < Func < Model, bool >> i) => DummyTable.Where(i.Compile()).FirstOrDefault());
- return repository.Object;
- }
- }
- }
- IOC - interface and class mapping
Here, we map interfaces with the generated mock objects, which also stores mockdata with them
- class StructureMapTestRegistry: Registry {
- / <summary> /
- Initializes a new instance of the < see cref = "DependencyConfigurationRegistry" / > class. / < /summary>
- public StructureMapTestRegistry() {
- MockEntities.LoadData();
- For < IUserRepository > ().Use(MockRepositoryGenerator < UserModel > .RepositoryMock < IUserRepository > ());
- For < IUnitOfWork > ().Use(MockGenerator.UnitOfWorkMock());
- For < IDataContext > ().Use(MockGenerator.DataContextMock());
- }
- }
- Create IOC container for test project.
- public static class TestBootstrapper {
- public static void TestConfigureStructureMap() {
- ObjectFactory.Container.Dispose();
- ObjectFactory.Initialize(o => o.AddRegistry(new StructureMapTestRegistry()));
- ObjectFactory.Container.AssertConfigurationIsValid();
- }
- }
- Now writing test cases will be quite simpler
- Test cases for Service layer are shown below.
- [TestClass]
- public class UserServiceTest {
- private IUserService domainService;
- / <summary> / Initialize() is called once during test execution before / test methods in this test class are executed. / < /summary> [TestInitialize]
- public void Initialize() {
- TestBootstrapper.TestConfigureStructureMap();
- AutoMapperInit.BuildMap();
- domainService = ObjectFactory.GetInstance < IUserService > ();
- domainService.UserRepository = ObjectFactory.GetInstance < IUserRepository > ();
- domainService.UnitOfWork = ObjectFactory.GetInstance < IUnitOfWork > ();
- }
- [TestMethod]
- public void Save_AddNewUser_returnsSaveUser() {
- var response = domainService.Save(new UserViewModel() {
- Id = 0, FirstName = "Mak11", LastName = "Vichare11"
- });
- Assert.AreEqual("Vichare11", response.ViewModel.LastName);
- }
- [TestMethod]
- public void Save_UpdateExistingUser_returnsSaveUser() {
- var response = domainService.GetById(5);
- response.ViewModel.FirstName = "Makrand";
- var newResponse = domainService.Save(response.ViewModel);
- Assert.AreEqual(response.ViewModel.FirstName, newResponse.ViewModel.FirstName);
- }
- }
- Test cases for Repository layer are shown below.
- [TestClass]
- public class UserRepositoryTest {
- private IUserRepository repository;
- [TestInitialize]
- public void Initialize() {
- TestBootstrapper.TestConfigureStructureMap();
- repository = ObjectFactory.GetInstance < IUserRepository > ();
- }
- [TestMethod]
- public void Add_AddNewUser_returnsSaveUser() {
- var model = repository.Add(new UserModel() {
- Id = 11, FirstName = "Mak", LastName = "Vichare"
- });
- Assert.AreEqual("Vichare", model.LastName);
- }
- [TestMethod]
- public void Update_UpdateExistingUser_returnsSaveUser() {
- var oldModel = repository.GetById(5);
- oldModel.FirstName = "Makrand";
- var model = repository.Update(oldModel);
- Assert.AreEqual(oldModel.FirstName, model.FirstName);
- }
- }
- MVC controller Test cases are shown below.
- [TestClass]
- public class UserControllerTest {
- private UserController userController;
- IUserService userService;
- [TestInitialize]
- public void Initialize() {
- userService = ObjectFactory.GetInstance < IUserService > ();
- userController = new UserController(userService);
- MockControllerHelpers.RegisterTestRoutes();
- }
- [TestMethod]
- public void Edit_updates_the_object_and_returns_a_JsonResult_containing_the_redirect_URL() {
- Arrange
- userController.SetMockController("~/User/Edit");
- Act
- var result = userController.Edit(1);
- Assert
- Assert.IsInstanceOfType(result, typeof(JsonResult));
- }
- }
Final Thought
In the real project, this approach can be implemented in various ways. You don't have to implement it completely. If you don't agree with this approach 100%, then also you can pick up a few things like, using IOC for the creation of mock objects or repository layer test cases.
Let me know what you think and suggest about this style.