Today I will discuss a very important topic, without knowing which, you will not learn how to write good unit tests. As you have read in previous articles, unit tests should not touch external resources. So how to test the logic in methods that have references to external resources? You have to use artificial objects, so-called mocks.
What is mocking?
Mocking, i.e. imitating something or some behavior. In Polish translation, you can find various translations of the word mock, including mock, but I will simply use mock. If a method has logic in it and uses external resources (such as a database, file, web services, etc.), then such a method would not be able to be tested in our unit tests. However, if we replace an external resource with an artificial object that has no logic, then we will be able to test such a method. For this to be successful, our code must follow certain rules. Among other things, it must have loose connections and operate on interfaces. If we follow these rules, we can replace the implementations for testing purposes. Therefore, if we want to add tests to some applications that were not written with tests in mind, this is where a problem may arise. We need to refactor the code, which can be a big challenge without prior testing :)
C# mocking frameworks
We have many different mocking frameworks to choose from, including Moq, NSubstitute, FakeItEasy, Rhino Mocks and many others. I think it is worth taking an interest in the Moq framework, which I use every day and I recommend it to you as well. It has all the functionality I need for mocking objects and is easy to use.
Code with external dependencies in C#
It will be easiest to learn how to mock using a specific example. Let's assume we want to test the application login method. To simplify the example, let's say that the method returns a message for bad data and an empty string for good data. Our class might look like this:
public interface IUsersRepository
{
bool Login(string user, string password);
}
public class Authentication
{
private readonly IUsersRepository _usersRepository;
public Authentication(IUsersRepository usersRepository)
{
_usersRepository = usersRepository;
}
public string Login(string user, string password)
{
var isAuthenticated = _usersRepository.Login(user, password);//data from database
if (!isAuthenticated)
return "User or password is incorrect.";
return string.Empty;
}
}
We have an Authentication class whose constructor accepts an object of a class implementing the IUserRepository interface, this class is responsible for logging in. The Authentication class has one Login method, which returns an appropriate message based on whether we are successful in logging in. If the login data are correct, an empty string will be returned, otherwise a message that the data is incorrect.
Unit test example, with external dependencies in C#
If we wanted to check in a unit test whether we would actually receive the appropriate message, we would have to use an external resource (database) and then our tests would no longer be unit tests. It's good that the above code was written by a good programmer :) who knew that it was necessary to operate on interfaces so that the class was testable. We can replace the IUsersRepository implementation and use a mock interface instead. Our test code might look like this:
public class AuthenticationTests
{
[Test]
public void Login_WhenIncorrectData_ShouldReturnCorrectMessage()
{
//Arrange
var mockUserRepository = new Mock<IUsersRepository>();
mockUserRepository.Setup(x => x.Login("user", "password")).Returns(false);
var authentication = new Authentication(mockUserRepository.Object);
//Act
var result = authentication.Login("user", "password");
//Assert
Assert.That(result, Does.Contain("User or password is incorrect"));
}
}
First, we need to install the testing framework. You need to search through Manage NuGet Package - Moq, then install it.
As you can see in the first line of arrange, the Moq framework is used to initialize a mock object. Then we define how the Login method of our object should work. That is, we say that after calling the Login method for specific parameters, in our case login - user, and password - password, return false. In the next line, we pass the mock object as a parameter of the Authentication class. We call the login method and check whether the result contains the given message. Our unit test passes correctly, we do not refer to any external dependencies, and we managed to test the logic contained in this test.
Notice how important it is to operate on the interface in this case. If the Authentication class, instead of operating on the IUserRepository interface, worked on a regular class, it would not be possible to use mocks, and therefore unit tests.
Let's see what an example might look like when the user provides the correct password and login, then an empty string should be returned.
[Test]
public void Login_WhenCorrectData_ShouldReturnEmptyString()
{
//Arrange
var mockUserRepository = new Mock<IUsersRepository>();
mockUserRepository.Setup(x => x.Login("user", "password")).Returns(true);
var authentication = new Authentication(mockUserRepository.Object);
//Act
var result = authentication.Login("user", "password");
//Assert
Assert.That(result, Is.Empty);
}
In this example, in the configuration of the Login method we say that we want our method to return true if it is called with the user and password parameters. Thanks to this, we can test the path that interests us.
Note that if we call the Login method with different parameters in act, our test will be red. In arrange, we have configured the Login method to return true only for the user and password parameters; if the parameters are different, then the default value, i.e. false, will be returned. See this in an example:
[Test]
public void Login_WhenCorrectData_ShouldReturnEmptyString()
{
//Arrange
var mockUserRepository = new Mock<IUsersRepository>();
mockUserRepository.Setup(x => x.Login("user", "password")).Returns(true);
var authentication = new Authentication(mockUserRepository.Object);
//Act
var result = authentication.Login("1", "2");
//Assert
Assert.That(result, Is.Empty);
}
This code doesn't go through. We receive the message: "Expected: But was: User or password is incorrect". We can also configure the call of this method for different parameters, thanks to It.IsAny<string>() this method can return the same result for all strings. This is what the whole implementation looks like:
[Test]
public void Login_WhenCorrectData_ShouldReturnEmptyString()
{
//Arrange
var mockUserRepository = new Mock<IUsersRepository>();
mockUserRepository.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>())).Returns(true);
var authentication = new Authentication(mockUserRepository.Object);
//Act
var result = authentication.Login("1", "2");
//Assert
Assert.That(result, Is.Empty);
}
The test result is green. Thanks to this configuration, no matter what parameters the attached method is called in act, true will be returned for each string parameter.
Of course, we also need to test the login method that we are mocking in this case, but we will do it in integration tests, where we will check logging in on a real database. In this example, we want to test the remaining code, i.e. the logic in the Authentication class, and for this we need to get rid of external dependencies.
Verifying mock calls
Thanks to mocks, we can also verify how many times a mocked method was called, or whether it was called at all. To represent this, I'll change the test class a bit.
public interface IUsersRepository
{
bool Login(string user, string password);
void UpdateLastLoginDate(string user);
}
public class Authentication
{
private readonly IUsersRepository _usersRepository;
public Authentication(IUsersRepository usersRepository)
{
_usersRepository = usersRepository;
}
public string Login(string user, string password)
{
var isAuthenticated = _usersRepository.Login(user, password);//data from database
if (!isAuthenticated)
return "User or password is incorrect.";
_usersRepository.UpdateLastLoginDate(user);
return string.Empty;
}
}
The UpdateLastLoginDate(string user) method has been added to the IUserRepository interface, which updates the last login date for a specific user. In the Login method, the UpdateLastLoginDate method should only be called when the user logs in. Let's check if this actually happens:
[Test]
public void Login_WhenCorrectData_ShouldUpdateLastLoginDate()
{
//Arrange
var mockUserRepository = new Mock<IUsersRepository>();
mockUserRepository.Setup(x => x.Login("user", "password")).Returns(true);
mockUserRepository.Setup(x => x.UpdateLastLoginDate("user"));
var authentication = new Authentication(mockUserRepository.Object);
//Act
var result = authentication.Login("user", "password");
//Assert
mockUserRepository.Verify(x => x.UpdateLastLoginDate("user"), Times.Once);
}
Thanks to the Verify method, we can check how many times a given method has been called. In the above case, we were interested in ensuring that the Login method was called exactly once, and this actually happened. You might as well check that a given method has not been called even once:
[Test]
public void Login_WhenIncorrectData_ShouldNotUpdateLastLoginDate()
{
//Arrange
var mockUserRepository = new Mock<IUsersRepository>();
mockUserRepository.Setup(x => x.Login("user", "password")).Returns(false);
mockUserRepository.Setup(x => x.UpdateLastLoginDate("user"));
var authentication = new Authentication(mockUserRepository.Object);
//Act
var result = authentication.Login("user", "password");
//Assert
mockUserRepository.Verify(x => x.UpdateLastLoginDate("user"), Times.Never);
}
We verify that if incorrect data is provided, the UpdateLastLoginDate method will not be called.
Unit Testing School
If you would like to learn how to write unit tests in C#/.NET applications, consider joining the Unit Testing School - [here].
SUMMARY
Mocking objects in the world of unit testing is a very important topic. We often need to replace external dependencies with some artificial behavior. In today's article, I demonstrated with simple examples how this should be done in .NET, using Moq. I use the Moq framework, but if you want, you can of course use another framework. I recommend testing it and starting using the one that suits you best. They all have the same goals, i.e. mocking objects, but they differ mainly in syntax. If you have any questions about mocking or are missing more information or examples, please let me know in a comment. In the next article I will show you how to write integration tests in .NET. We will test the code that will add data to the database using the Entity Framework.