Unit tests are used to test the functionality of an individual function or a collection of functions. This will be a simple introduction to unit testing, where we use TDD to implement a basic greeter method.
This article assumes you are working in a repository forked from LBHackney-IT/lbh-base-api.
Watch the video version of this page if you prefer:
Create a new file somewhere, named
usingstatements at the top provide references to things we will use later on.
- At HackIT, we typically use a separate
<ProjectName>.Testsnamespace to contain test code.
This doesn't test anything yet! We need to add some more code.
Create the test class, and the initial signature of the test method.
- Two of the lines above are highlighted. These
[Symbols]are called Attributes; they are part of the C# Language.
We are using the NUnit unit-testing framework in this
example, so we use
[TestFixture] attribute to indicate to NUnit that we are
writing a test class; and
[Test] to indicate that we are writing a test
Also, notice the test method name. The name should be descriptive and indicative of the desired effect of calling the method.
Now, add the actual test code!
First, we call the method we are testing, and assign its result to the
Then, we compare the result we captured with the result we want the method to
Should().BeEquivalentTo()syntax is provided by
FluentAssertionsextension methods. It makes our Assert lines read more nicely.
- Your IDE or Text Editor might complain when you write these lines, because
GetGreetingForNamemethod exists yet. This is normal, and is actually part of the TDD process!
Now that we have some test code, we can try to run it.
dotnet testthis time exited before any tests were run, so the output here is a compilation error, rather than being from the testing framework itself. However, we can treat this as a failing test, and solve the problem that the output describes.
The output tells us that
GreetingGateway doesn't exist. That's true - we
haven't made it yet!
We will write the most minimal piece of code that will pass the test.
- We are using the same names for the class and method that we wrote into the test method.
We have addressed the error in the previous test run's output by creating the
class that didn't exist,
GreetingGateway (and added the method pre-emptively,
to save some test runs).
All of the tests in the project have passed! Our new code works, and it doesn't break anything that already existed in the codebase. Good news?
At this point, it probably seems like the method is wrong. It only works for James, and it should greet anyone. In reality, though, the method is fine - it's the test that is wrong!
We need to make sure that this greeting method works regardless of the name of the person it is greeting. Since this is a requirement of the feature, the test should enforce it.
What we want to do, is pass a name into
GetGreetingForName, and have it return
an appropriate greeting for that name.
- We've used string
in the Assert, so that whatever we set
nameto, will be what we test the
And we are back to a compilation error.
This time, it's because the method we've written for the implementation doesn't take any arguments, but we are trying to use one in the test! Let's fix that.
Now the method takes the name to be greeted as an argument.
We are still getting a compilation error, because the compiler mandates that we use the parameter we added to the method.
This can be done using exactly the same interpolation used in the test itself.
Check the tests again:
This passes! 🎉
To be pedantic, the test we currently have only ensures that the method will work for people named Lisa. This is a simple example, so we can see quite clearly that this would work for other names as well.
In other scenarios, it makes sense to test more thoroughly, covering as many use cases as possible. One approach that can help with this is to generate lots of different inputs to the method, and check that they all work. This can help uncover strange edge cases.
We can use a library called Bogus to help with this.
In this example, we use Bogus to pick a random first name to assign to the
name variable. Every time the test is run, we assert that the method runs for
the random name.
Everything is still passing! The feature is complete and we have a robust test for it.