Robert C. Martin argues in his book “Clean Code” (2009, page 133) that writing clean test-code is perhaps even more important than writing clean production-code, “because tests preserve and enhance the flexibility, maintainability, and reusability of the production code”. I very much agree with him. Even when assuming that all the business logic in the production code are covered by automated tests, the code will be hard to extend or maintain if the test-code is poorly written.
Test-code with code-duplication, lack of readability, non-intuitive naming conventions, or lack of formatting, to name a few, will lead to fear of maintaining or extending the production code, difficulties adapting tests to new requirements, and may in worst case even lead to the discarding of existing tests and writing new ones. In other words, if the test-code is not written cleanly, one can argue the entire code-base is in a rotten state.
Mocking is the practice of simulating and verifying behavior of objects, mostly as a part of automated testing. It enables a loose coupling between dependent components by being in full control of its implementation-specific details,
instead of relying on any real-world implementation, which may change or be replaced over time. Also, mocking requires minimal resources, thus leading to tests executing fast.
This blog post will illustrate one way of making test-code cleaner by reusing code for setting up and verifying mocks for automated tests, and splitting the code into small functions with appropriate names. It may hopefully present some concepts of how to make test-code cleaner, which is arguably something any professional developer should strive for.
I will not go into detail how to create a solution, solution folders and project, and will merely focus on the code itself. The code example is written in C# for the .NET Core 3.0 preview, but the principles that the code illustrates should apply to all automated tests using mocks, regardless of the language they are written in. The code example is trying to have a balance between showing as less code as possible, and having a somewhat real-world scenario.
The example will use the mocking framework for C# called “Moq”.
Testing and implementing business logic with an interface contract
The functional requirement we are implementing in this example is implementing functionality for adding a new customer through an existing CustomerRepository interface.
Let us start off with creating a simple interface for a customer repository, which could be used as a contract for persisting and retrieving customers from a database:
Next, we will add the Customer class that is needed for the methods in the interface. We will add two fields, “Id” and “Name” for this class:
Our goal is to create a fully functional CustomerService class that will contain our business rules for how customers should be added. In other words, we should create a CustomerService class with an “Add” method, which should invoke the ICustomerService.Add method down the line.
We will use a test-driven development approach, thus never writing any production code before we have one failing test. We will start writing a test for the CustomerService.Add method, that will succeed by adding the customer without errors. We will create a “CustomerServiceTests” class in our test/CleanMockingTests project, with the following test:
The “CreateRandomCustomer” method simply creates a customer with a random “Id” and “Name”. This method assign random GUID strings as values to the fields, and is included in the final code in the GitHub project.
You will notice that this test does not compile, due to missing class “CustomerService”, and thus the missing method “CustomerService.Add”. Let us create the class so that the test compiles:
Now our test will compile, but it will fail on the customerRepositoryMock.Verify method in our test. This is good, because we want our test to fail before implementing the code. Now, let us implement the minimal required code necessary to make our test pass:
If we run our test again, it should pass.
Next, let us write a new test for the CustomerService.Add method to throw an exception if the customer being returned from the ICustomerRepository.Add is null:
Obviously, the new test we wrote will fail at the first assertion, as the production code is not implemented yet. Let us write the code necessary for the test to pass (I have included refactoring as a part of the implemented code, but the refactoring was something I did after I made the test pass with minimal effort):
Run our tests again, and both should now pass.
As you may have noticed, we have started copy-pasting code for setting up and verifying methods on our ICustomerRepository mock in our tests. The code is starting to get messy and smelly due to the code-duplication, but also in terms of the readability of our mock-code, even though this is one of the most light-weight mock examples — imagine our ICustomerRepository.Add method taking more arguments than one, or the CustomerService class being dependent on more than one interface!
Writing our own Mock-API for the ICustomerRepository interface
We should start refactor our test-code immediately to eliminate code-duplication, and make our code more intuitive. I have chosen to make my own mock-class that simply extend the Mock<ICustomerRepository> class, that contains my own methods for setting up the mock, and verifying method invocations:
This class extends the Mock<ICustomerRepository>, which means that we inherit all the functionality from this parent class. We have two distinct public methods that we will use in our tests:
- SetupAddReturns -> setting up the mock to return a given Customer when the ICustomerRepository.Add has been called with a specific Customer argument
- VerifyOnlyAddWasCalledOnce -> verify that the ICustomerRepository.Add with a specific Customer argument was called once, and that no other calls were made on the interface
Now, before we refactor our test-code let us have a look at the entire CustomerServiceTests class:
Our main priority for refactoring our test-code is to eliminate duplication and increase the readability. We obviously want to replace the “Setup” and “Verify” methods of our ICustomerRepository mocks with the functionality we implemented in our CustomerRepositoryMock class. While we are at it, let us also remove the duplicate code for creating a CustomerService instance. Our refactoring will result in the class looking like this:
We are now creating instances of our own mock-class, “CustomerServiceMock”, which means we can call the methods we implemented earlier for setting up and verifying the ICustomerRepository.Add method; SetupAddReturns and VerifyOnlyAddWasCalledOnce.
Our code is free of duplication, the mocking code is easier to read, and arguably easier to understand and maintain and extend for both ourselves and other developers.
We have looked at how we can write clean code for mocking functionality in our automated tests, by removing duplication, creating our own mock-class based on the Moq framework, and making the code easier to read, maintain and extend.
The code example in this post was purposefully as simple as possible to avoid showing too much code and making the main point confusing, but the effect in terms of code-readability will be even greater when you are mocking several interfaces, and/or several methods for each interface you are mocking. However, the principles of removing code duplication, improving readability, and making the code as extendable and maintainable as possibly should apply regardless of complexity.
I hope this blog post was useful, and would welcome any constructive feedback.
The full code-example is located on GitHub in the “cleanmocking” project.