Writing unit tests with NUnit and NSubstitute

Imagine you are a Junior .Net Developer and you just started your development career. You got your first job and you are given a task – write unit tests!

Nothing to worry about, since you got me. I’ll show you how things are done and what are the best practices to follow.

Introduction

Writing unit tests is crucial to develop high-quality software and maintain it according to business requirements. Tests are the tool for developer to quickly check a small portion of code and ensure that it does what it should. In many cases tests can be unnecessary, requiring maintenance, without any gained value. However, writing tests is a standard and art that every developer should master.

Note, that writing unit tests is easy only when the code to test is written in a correct way. When methods are short, do a single thing and don’t have many dependencies, they are easy to test.

To write unit tests in this post I’ll use NUnit and NSubstitute. Those are two very popular nuget packages, that you can easily find. All code will be written in .Net Core.

Following the AAA pattern

The AAA (Arrange, Act, Assert) unit test writing pattern divides every test into 3 parts:

  • Arrange – in this part, you prepare data and mocks for a test scenario
  • Act – executing a single action that we want to test
  • Assert – checking whether expectations are met and mocks were triggered

Let’s have a look at a simple code, that we will test:

And a simple test would look like this:

This is a simple test, that checks whether the result is correct. There is a TestFixture attribute, that indicates, that this class contains a group of tests. a Test attribute marks a single test scenario.

  • in Arrange part we prepare productNameProvider and parameters
  • in Act there is only a single line where we execute GetProductName, which is a testing method
  • in Assert we use Assert.AreEqual to check the result. Every test needs to have at least one assertion. If any of the assertions fails, the whole test will fail

Test edge-cases

What you could see in an example above is a happy-path test. It tests only an obvious scenario. You should test code when a given parameter is not quite what you expect. The idea of that kind of tests is perfectly described in this tweet:

Let’s see an example with a REST API controller method. First, let’s see code that we would test:

This is a standard Post method, that adds a product. There are some edge-cases though, that we should test, but first let’s see how happy-path test would look like.

Notice  that I added:

SetUp method will be run before every test and can contain code that we would need to execute for every test. In my case, it is creating mocks and setting up some mocks as well. For example, I set up a logger in order to be able to test it later. I also specify, that my ILogger mock will be returned whenever I create a logger.

I could do that in Arrange part of the test, but I would need to do it for every test. In Arrange I set up a _productService mock that returns guid:

And later in Assert, I check, that this method was in fact called. I also check the result of an Acting part.

Now, let’s see how we can test an edge case, when a developer using this API, will not provide a value.

In one line we both act and assert. We can also check exception fields in next checks:

The important part is to set Returns values for mocks in Arrange and check mocks in Assert with Received.

Test Driven Development

To be fair with you I need to admit that this controller method ist’s written in the best way. It should be async, have parameters validation and try-catch block. We could turn our process around a bit and write tests first, that would ensure how the method should behave. This concept is called Test Driven Development – TDD. It requires from developer to write tests first and sets acceptance criteria for code that needs to be implemented.

This isn’t the easiest approach. It also expects that we know all interfaces and contracts in advance. In my opinion, it’s not useful in real-life work, maybe with one exception. The only scenario I’d like to have tests first would be a refactoring of an old code, where we write one part of it anew. In this scenario, I would copy or write tests to ensure that new code works exactly the same as the old one.

Naming things

Important thing is to follow patterns that are visible in your project and stick to it. Naming things correctly might sound obvious and silly, but it’s crucial for code organization and it’s visibility.

 

 

 

First, let’s have a look at the project structure. Notice that all test projects are in Tests directory and names of those projects are same as projects they test plus “Tests”. Directories that tests are in are corresponding to those that we test, so that directory structure in both projects is the same. Test classes names are also the same.

 

 

 

 

 

Next thing is naming test scenarios. Have a look test results in Resharper window:

In this project, every class has its corresponding test class. Each test scenario is named in such pattern: [method name]_[input]_[expected result]. Only looking at the test structure I already know what method is tested and what this test scenario is about. Remember that the test scenario should be small and should test a separate thing if that’s possible. It doesn’t mean that when you test a mapper, you should have a separate scenario for every property mapped, but you might consider dividing those tests to have: happy-path test and all the edge cases.

 

That’s it! You are ready for work, so go and write your own tests:)

 

 All code posted here you can find on my GitHub: https://github.com/mikuam/unit-testing-examples

You can play with a code a bit, write more classes and tests. If you like this topic or you’d like to have some practical test assignment prepared to test yourself, please let me know:)

 

3 thoughts on “Writing unit tests with NUnit and NSubstitute

  1. Pingback: dotnetomaniak.pl

Leave a Reply

Your email address will not be published. Required fields are marked *