How to have more fun with UnitTests in dotnet core
I rather not to put too many lines in the intro of this post and just get to the sweet parts directly.
But I have to confess that when you get used to do UnitTests, specially with TDD; it is so hard not to do it. It is so hard that if your manager tells you to skip the tests, you may say “If the tests go, I go” 😂. Well maybe not that far but you got the point 😉 .
For me mostly it buys more time when the product owner comes with new features. Because by just looking at the tests I know what the code does so far and I would know where this change (either a new feature or changing an existing one) would fit in.
With that being said, let’s get started.
Wait! Are you a visual learner?
well no worries. Here you go, you can go and check my videos related to this post and enjoy coding for the rest of your life 😅
And before I forget, the code in this post is available on Github, in case you want to do some tests on your own.
Well that’s enough, let’s get to the fun part.
Environment
So we are going to use the following stack:
Language: C#
Framework: dotnet 6
IDE: beloved vscode 😅
Unit testing framework: xUnit
I think the only one which would need a short description would be “why xUnit?!”
Well, really?
So let me explain. xUnit isolates each test or “Fact" as it is running like an incoming request and it doesn’t depend on the other tests.
The basics you need to prepare for your tests are:
- Reading from
appsettings.json
. This is specific right now for our case, since we want to initiate aDbContext
with a connection string. Build theIConfiguration
. - Initiate the
DbContext
.
Now to read the appsettings with ConfigurationBuilder
you would do the following:
Now since the test project normally does not have an appsettings.json
you need to include it in your builds. To do this you need to add the following to your .csproj
.
Ok, so now that we can read the configuration in our test project, let’s use it and build a DbContext
.
Building the DbContext is also pretty simple but before we continue you need to reference the project you want to test. Run the command below in your test project.
dotnet add reference PATH_TO_PROJECT_UNDER_TEST
Now to build the DbContext normally I create a DbContextFactory.cs
because you need some methods there.
The first thing in the factory obviously is initializing it so it would as easy as this:
In the above snippet we already have passed the Configuration
to the factory from the constructor. The above code is good to be inside a private method so you can use it with the following methods.
The above methods are really important. When you want to load an entity and pass it to your SUT (System-Under-Test)
those entities should no longer be tracked by any DbContext
because that is how it is in real run. The data which comes to your function is coming mostly from an already processed source (UI, another service or ….) .
Note: NOT doing the above, once got me into a misunderstanding and lead into a disaster. Read the following post if you want to know about it.
So now its time to automate your migrations in UnitTests.
create a public method in your DbContextFactory.cs
and put the following code there.
So the above method first will check if the Database is reachable. if it is not reachable one reason could be that it is not yet created so it would try to migrate the database. Now if the database is already there it will check if there is any new migrations to apply then if there are some it will try to run the migration.
This way if you actually also have some data on your UnitTest database, you will test the migrations too somehow.
public abstract class TestBase {}
This class will be the abstract class which all your tests will extend.
You don’t want to have the same code copy-pasted in each test class right? because you are a wise developer 😉.
So you are going to setup the TestBase
like this,
So as you see the differenct between xUnit and nUnit here is that there is not Setup
or TearDown
. It is intelligence! So the constructor of the class is the Initiator
and in case you need a TearDown
you can implment IDisposable
.
One thing to notice here is the Collection
attribute on the TestBase
class. What it does is that it will make all the tests to run Serially
and not in parallel. Why we need this? well since in this case we are using a real database, if the tests run in parallel you will get into issue with CleanDatabase
method. For example the Test1
and Test2
are running. Test1
is done running and Test2
is still being ran. Meanwhile Test3
is started to run and will call the CleanDatabase
since it is on the constructor. Now Test2
will fail because Test3
removed it’s data.
You also can use InMemoryDatabase
built into EfCore but you have to becareful that since it is not your production database, specially with some queries you may get positive results in tests but failure in prod.
Consider this simple case
dbContext.People.OrderByDesc(x => x.ModifiedOn).ToList();
The above would run fine in InMemoryDatabase
but it will fail on mssql since EfCore’s mssql driver cannot translate it to a sql statement.
Ok, now that we have the Tests infrastructure in place, let’s create the first UnitTest.
First UnitTest
Let’s say that you want to create a person.
Before we continue and create a person, I want to introduce you to another library which I use on the service itself.
This is a briliant way to isolate your methods from each other.
Now let’s say you know what CQRS
is and you are using Mediator
to implement a command
for creating a person. Let’s call it CreatePersonCommand
.
Before you code this command, you gonna have a NotImplementedException
on the Handler
of this command and that’s enough for now to create your test.
On the UnitTest project I normally create a test class with the same name for the system under test
. So we call this test classCreatePersonCommandTests
.
Well if you have noticed, we are trying to do TDD
or Test Driven Development
. Why, you ask? Well, when you master this method, you will learn how to break down your method into smaller pieces.
It means you will have the big picture about the person creation but you will do it step by step.
- Create the person with given fields
- If the dto contains an image an ImageId should be assigned on the Person record.
So let’s start with the simple case. Let’s assume the Person entity has FirstName
and LastName
.
So the test would look something like:
Not much is going on. If more goes on than this, then your UnitTest gonna need UnitTest 😅.
The below library is used to do Assertions
.
The part which is under test, is line 6
and this test will fail 😱. You have not yet implemented anything right? This is the point in TDD
. You will right a failed test and will try to make it pass (Programming Gamified🕹️).
When you made it pass, it means that there is a high chance that your code gonna do whatever you expected.
Now let’s say you want to upload a picture and assign the ImageId
to the person. We will need to mock it up 💪
Mocking methods in dotnet core
Let’s say you have tested and implemented an UploadImageCommand
. Since you are a wise developer and don’t want to implement the same logic inside the CreatePersonCommand
you will call this command inside CreatePersonCommand
.
Now a BIG QUESTION MARK will pop.
“Since I want to now right a test to see if the ImageId will be added to the person or not should I actually run the UploadImageCommand
??!! ”
Well my friend the answer depends on your TestLevel
. Do you want an IntegrationTest or a UnitTest.
Since here we are talking about UnitTest, the answer would be NO.
You need to Mock
the UploadImageCommand.Send()
method.
To mock, I use the following library. Pretty easy and straight forward to use with a huge community.
So now let’s create the following test ShouldHaveAssignedAnImageIdToThePerson
.
So the above test would fail since it is not implemented yet.
After you implement the method you will notice that you are going to need to pass the IMediator
to your test because now it won’t even build.
But here is where you need to do a Mock.
You need to mock IMediator
and “pretend ”that the UploadImageCommand
has ran.
To mock IMediator
and just get an instance you need to do the following (I’m basically documenting the Mock
library :D )
var mediator = new Mock<IMediator>().Object;
and you can pass the mediator
to CreatePersonCommand
. BUT the test will still fail. You need to Setup
the send method.
After doing the setup, now do the following to get the object
var mediator = mediatorMock.Object;
The above snippet will setup the Send
method BUT your test still will fail. The mocked method in CreatePersonCommand
will return a random Guid
and will set it on the PersonId
but you will get an exception from EfCore
and theDatabase
since the ImageId
doesn’t exist (Only if you have a constraint here. Otherwise you are good to go).
I would leave this to you to do the right way to mock your Mediator in a way to resolve the Constraint
issue 😉
Don’t hesitate to ask questions though. But try to get creative and come up with a solution. One of the main points of doing these kind of tests is also to know how the code is functioning.
Conclusion
I think at the end I would say the main reason that people avoid UnitTesting is due to not knowing how to start. It seems like something extra to code; But I would say when you get used to do it, eventually you would code efficiently and it’s no longer about coding more or less 😉.
Have fun coding ❤️