DEV Community

Lucas Fonseca Mundim
Lucas Fonseca Mundim

Posted on

Writing your F.I.R.S.T. Unit Tests

What are Unit Tests?

By design, Unit Tests are automated tests of small units of code that are tested in an isolated fashion. Essentially, an unit test is a program that calls the (public) methods of a class and checks if the results are as expected


Benefits to Unit Testing

If you wrote your unit tests correctly, there are several benefits they will bring to your maintenance process:

  • Unit Tests will find bugs while you're still developing: if you are refactoring a section of code, or even including a new one, and your past tests start to fail, it means that you changed the behaviour of that method and that might imply on a bug!
  • Any correction cost regarding the newly found bugs is considerably lower than if they were found while in production
  • In other words, Unit Tests protect your code against Regression: system changes that create bugs
  • After changing your code, you must run the test suite! That way you'll find if there was any regression (if any test fails)


F.I.R.S.T. Principles

Just as we have our S.O.L.I.D. principles for programming in general, there are a couple of principles for general good practice in unit testing: the F.I.R.S.T. principles. Let's go over them.


The developer should not hesitate on running the test suite at any point of the development cycle, even if there are thousands of unit tests. They should run in a matter of seconds. If a test takes too long to run, it probably is doing more than it should — and, therefore, is not an unit test!


For any given unit test, it should be independent of everything else so that its results are not influenced by any other factor. With that definition, they should usually follow the “3 A’s of testing”: Arrange, Act, Assert (also known as “Given-When-Then”).

  • Arrange: All needed data should be provided to the test when you are about to run, and it should not depend on your environment.
  • Act: The actual method you are testing should be run.
  • Assert: Unit Tests should only test one outcome, meaning each test should assert that one state of an object should be tested. Note that this does not mean that you should only check one variable of a class response, but that all variables tested should be related to the method you ran


Tests should be repeatable and deterministic: every single time you run a test their values cannot change, no matter the environment.


The test itself should tell you if it passed. There should not be any need of manually checking the values. Most assertion libraries (such as Shouldly) work in favour of that principle.


Your test should cover all “happy paths” of a method (xUnit makes that possible with Theory tests), all edge cases (where you think the test might fail), illegal arguments, security flaws, large values… in sum, every possible use case scenario should be tested, not only enough to have (near) 100% Code Coverage

The extra T: Timely

Following TDD, Unit Tests should be written before the production code that will be tested. This can be done by writing the abstraction (Interface) and then the test based on the method signature and spec alone. After the test is written, your code can be produced and tested.


A few tools

For C#/.NET development, a few tools/frameworks are available by default, and a lot more available as NuGet packages. Here are some highly recommended options

  • Base Framework: xUnit is a powerful and complete testing framework for .NET that is usually bundled with VisualStudio alongside NUnit. Overall both are good frameworks, but xUnit is more readable and intuitive
  • Assertion Tool: Shouldly is a much more intuitive way of asserting your test results than the native Assert class. The github repo gives plenty of examples but just one very simple sample should be enough to show how readable and simple Shouldly is:
// using Assert
// using Shouldly
Enter fullscreen mode Exit fullscreen mode
  • Mocking Tool: NSubstitute is a powerful tool that allows you to create mocks for any given class or interface so that you can easily test any method that would, in a regular case, use external environment such as 3rd Party APIs or DataBases. It has a simple learning curve and is well documented


This article is a compilation made from different source materials cited below:

Top comments (0)