Good answers have been given, but I wanted to add another way of understanding unit tests.
My car won't start. It clearly doesn't work. But what do I need to fix? Are the cylinders getting fuel and air but not igniting? Is the fuel tank letting fuel into the engine? Is the exhaust blocked?
It's hard to figure out what's wrong when the only test you have is starting the whole car (this is the equivalent of an integration test). It would be much better if you could test each part separately to see if it performs its duty.
To do this, we test the following: the part gets all the input it needs, and we observe the output it generates. If the output matches our expectations, then the part works.
This follows the same pattern as basic functions in programming:
Input > Processing > Output
Take the example of a calculator, specifically the Add(int,int)
function. If we supply it with specific input (1,1
) and observe if it's output is what we expect (2
), then we can conclude that the Add()
method (i.e. the "processing" part) is working correctly.
For the car example, you'd do the same thing in real life.
- If you want to see if the fuel tank works, you take a tank (processing), put fuel in it (input), and see if fuel flows out the other end (observe output).
- If you want to see if the fuel tank can hold the expected amount of fuel, you take a tank (processing), put fuel in it (input), and ensure nothing comes out (observe output), and see if the amount of fuel you've been able to put in is (at least) the expected amount of fuel the tank should be able to hold.
- You can perform some other tests, such as checking if fuel doesn't exist from other places, but the "happy path" testing of the fuel tank is done by checking the intended place for fuel to come out.
When you understand the basic principle behind unit test, i.e. testing the processing based on given input and observed output, it should be more clear how you should structure your unit tests.
So what are you testing? If we strip away the parts that don't contribute to the assertions:
var user = new User();
Assert.IsAssignableFrom<User>(user);
That's not a meaningful test, as you've rigorously defined the type of the object whose type you're now checking. It can never fail. You're really only testing your ability to write a test. it's essentially the same as doing:
var sum = 1 + 1;
Assert.AreEqual(sum, 1 + 1);
But I think you intend to test something completely different, when I look at the other part of the test:
var userRepository = new Mock<IUserRepository>();
userRepository.Setup(s => s.CreateNewUser(user)).Returns(Task.FromResult(1));
If your test aims to test the repository, you shouldn't be mocking the repository. You mock the things that aren't being tested right now. You mock the dependencies of the thing you're trying to test.
For example, this mocking would be useful if your were testing e.g. a UserService
who has a dependency on an IUserRepository
. Assuming the following service:
public class UserService
{
private readonly IUserRepository _userRepo;
public UserService(IUserRepository userRepo)
{
_userRepo = userRepo;
}
public async Task<string> CreateUser(string name)
{
var user = new User(name);
var userId = await _userRepo.CreateNewUser(user);
return $"Created user with ID {userId}";
}
}
Your unit test is mocking the right dependency to test if the correct output message is given:
[Fact]
public void Return_message_contains_new_user_ID()
{
// Mocked dependency
int newUserID = 123456;
var userRepository = new Mock<IUserRepository>();
userRepository.Setup(s => s.CreateNewUser(It.IsAny<User>())).Returns(Task.FromResult(newUserID));
// Real object to test
var userService = new UserService(userRepository); // with the mocked dependency
// Perform the test with controlled input
var result = userService.CreateUser("John Doe");
Assert.AreEqual(result, "Created user with ID 123456");
}
Testing return messages isn't the best test (or software design), but this is just a simple example to prove that the assertions are executed on the output of the thing that we're testing.
The same pattern emerges here: we provide the input (John Doe
), we mock the dependencies (we force the fake repository to tell the service user 123456 was created) we observe the output (the message), and we test if the message is what we expect it to be.