Unit Testing Best Practices

Unit Testing Best Practices: A checklist

As part of this series on unit testing, we’ve covered a lot of subjects. From how to write your first unit tests to create test data with Builders to how to write better fakes. I hope I’ve helped you to start writing unit tests or write even better unit tests.

This time, I’m bringing some tips and best practices from my previous posts in one single place for quick reference.

1. On Naming

Choose a naming convention and stick to it.

Every test name should tell the scenario under test and the expected result. Don’t worry about long test names. But don’t name your tests: Test1, Test2, and so on.

Describe in your test names what you’re testing in a language easy to understand, even for non-programmers.

Don’t prefix your test names with “Test.” If you’re using a testing framework that doesn’t need keywords in your test names, don’t do that. With MSTest, there are attributes like [TestClass] and [TestMethod] to mark methods as tests. Other testing frameworks have similar ones.

Don’t use filler words like “Success” or “IsCorrect” in test names. Instead, tell what “success” and “correct” mean for that test. Is it a successful test because it doesn’t throw exceptions? Is it successful because it returns a value different from null? Make your test names easy to understand.

2. On Organization

Make your tests easy to find.

Put your unit tests in a test project named after the project they test. Use the suffix “Tests” or “UnitTests.” For example, if you have a library called MyLibrary, name your test project: MyLibrary.UnitTests.

Put your unit tests separated in files named after the unit of work or entry point of the code you’re testing. Use the suffix “Tests”. For a class MyClass, name your test file: MyClassTests.

Workbench full of tools
Keep your tests organized and easy to find. Photo by Barn Images on Unsplash

3. On Assertions

Follow the Arrange/Act/Assert (AAA) principle.

Separate the body of your tests. Use line breaks to visually separate the three AAA parts in the body of your tests.

Don’t repeat the logic under test in your assertions. And, please, don’t copy the tested logic and paste it into private methods in your test files to use it in your assertions. Use known or pre-calculated values, instead.

Don’t make private methods public to test them. Test private methods when calling your code under test through its public methods.

Have a single Act and Assert parts in your tests. Don’t put test values inside a collection to loop through it and assert on each one. Use parameterized tests to test the same scenario with different test values.

Use the right assertion methods of your testing framework. For example, use Assert.IsNull(result); instead of Assert.AreEqual(null, result);.

Prefer assertion methods for strings like Contains(), StartsWith() and Matches() instead of exactly comparing two strings.

4. On Test Data

Keep the amount of details at the right level

Give enough details to your readers, but not too many to make your tests noisy.

Use factory methods to reduce complex Arrange scenarios.

Make your scenario under test and test values extremely obvious. Don’t make developers decode your tests. Create constants for common test data and expected values.

Use object mothers to create input test values. Have a factory method or property holding a ready-to-use input object. Then, change what you need to match the scenario under test.

Prefer Builders to create complex object graphs. Object mothers are fine if you don’t have lots of variations of the object being constructed. If that’s the case, use the Builder pattern. Compose builders to create complex objects in your tests.

5. On Stubs and Mocks

Write dumb fakes

Use fakes when you depend on external systems you don’t control. Check your code makes the right calls with the right messages.

Avoid complex logic inside your fakes. Don’t add flags to your stubs to return one value or another. Write separate stubs instead.

Don’t write assertions for stubs. Assert on the output of your code under test or use mocks. Remember there’s a difference between stubs and mocks.

Keep one mock per test. Don’t use multiple mocks per test. Write separate tests instead.

Make tests set their own values for fakes. Avoid magic values inside your stubs.

Use descriptive names in your fakes. Name your stubs to indicate the value they return or the exception they throw. For example, ItemOutOfStockStockService and FixedDateClock.

Voilà! Those are my best practices for writing better great unit tests. Don’t forget to always start writing failing tests. And make sure they fail for the right reasons. If you don’t follow Test-Driven Development, comment out some of your code under test or change the assertions on purpose to see your tests failing.

We don’t ship our tests to end users. But it doesn’t mean we shouldn’t care about the quality of our tests. Unit tests got our back when changing our code. They’re our safety net.

Don’t miss the rest of my Unit Testing 101 series where I cover all these subjects in depth.

Ready to upgrade your unit testing skills? Write readable and maintainable unit test with my course Mastering C# Unit Testing with Real-world Examples on Udemy. Learn unit testing best practices while refactoring real unit tests from my past projects. No tests for a Calculator class.

Happy testing!