Basics
This is in-team conventions about unit testing for all backend services.
Main libraries🔗
- xUnit - Framework for unit testing
- Moq - Isolation framework
- FluentAssertions - Extension methods that allow you to more naturally specify the expected outcome
Unit Test basic structure🔗
Use plural postfix for test class name %ClassName%Tests
public class ClassNameTests
{
[Fact]
public void MethodName_StateUnderTest_ExpectedBehavior()
{
// test body
}
}
Info
Additional scenarios described in advanced section.
Characteristics of a good unit test🔗
- Fast. It is not uncommon for mature projects to have thousands of unit tests. Unit tests should take very little time to run (milliseconds).
- Isolated. Unit tests are standalone, can be run in isolation, and have no dependencies on any outside factors such as a file system or database.
- Repeatable. Running a unit test should be consistent with its results, that is, it always returns the same result if you do not change anything in between runs.
- Self-Checking. The test should be able to automatically detect if it passed or failed without any human interaction.
- Timely. A unit test should not take a disproportionately long time to write compared to the code being tested. If you find testing the code taking a large amount of time compared to writing the code, consider a design that is more testable.
Naming convention🔗
Naming standards are important because they explicitly express the intent of the test. Always follow Microsoft naming conventions. The name of your test should consist of three parts:
- MethodName - The name of the method being tested.
- StateUnderTest - The scenario under which it's being tested.
- ExpectedBehavior - The expected behavior when the scenario is invoked.
Info
Read Unit Test Naming Conventions article for more info.
Arrange-Act-Assert🔗
Arrange, Act, Assert is a common pattern when unit testing. As the name implies, it consists of three main actions:
- Arrange - your objects, creating and setting them up as necessary.
- Act - on an object.
- Assert - that something is as expected.
Always split you test on 3 sections.
public class MyTests
{
[Fact]
public void MethodName_StateUnderTest_ExpectedBehavior()
{
// arrange
// act
// assert
}
}
&
public class MyTests
{
[Fact]
public void MethodName_StateUnderTest_ExpectedBehavior()
{
// arrange & act
var result = 1 + 1;
// assert
result.Should().Be(2);
}
}
Info
Read Arranging Conventions article for more info.
Cover convention🔗
When you try to discover what logic should be covered follow these rules:
- Cover
uncommon logic. - Avoid cover
common logic.
What does this mean?
Common logic🔗
Common logic - logic provided by external sources.
This should NOT be covered:
- Methods provided external libraries, like:
- Archive() method of Zip library
- Send() method of ServiceBus library
- etc.
- DataBase logic:
- CRUD operations
- LINQ Query Operations
- etc.
- .NET logic like getters, setters, constructor arguments, etc.
- DTO - database entities, records and classes without logic
- API Controller methods - because there are should not be any logic
Uncommon logic🔗
Uncommon logic - logic written by us.
This SHOULD be covered:
- Public methods of implementations
- Business logic:
if-elsecasesdo-whilesectionstry-catchsectionsswitchexpressions- etc.
- Compile-time logic of external libraries. If it provides validation methods. Like:
- Service Provider (DI-container) configurations
- Mapster configurations
- Regular expressions
- Any discovered bugs should be covered by a unit test in the first place
Service Provider Unit Testing🔗
Each project should contain Service Provider Unit tests. Without it, we can get runtime errors. Example:
[Fact]
public void BuildServiceProvider_WithDependencies_ShouldNotThrow()
{
// arrange
var services = new ServiceCollection();
var serviceProviderOptions = new ServiceProviderOptions
{
ValidateOnBuild = true,
ValidateScopes = true,
};
services.AddYourProjectServices() // add your dependencies here
// act
var buildServiceCollection = () =>
{
var serviceProvider = services.BuildServiceProvider(serviceProviderOptions);
serviceProvider.CreateScope();
};
// assert
buildServiceCollection.Should().NotThrow();
}
Mapster Unit Testing🔗
Each project with Mapster should contain its configuration unit tests. Without it, we can get runtime errors. Example:
[Fact]
public void Compile_WithConfigurations_ShouldNotThrow()
{
// Arrange
var config = new TypeAdapterConfig
{
RequireExplicitMapping = true,
RequireDestinationMemberSource = true
};
config.Scan(Assembly.GetAssembly(typeof(Program))!); // add your configurations
// Act
Action compileAction = () => config.Compile();
// Assert
compileAction.Should().NotThrow();
}
GitlabCI (IN PROGRESS)🔗
How we should count code coverage? How we should write reports? Cobertura - tool that calculates the percentage of code accessed by tests. coverlet
FAQ (IN PROGRESS)🔗
Any questions.
Links🔗
Base information🔗
- xUnit getting started guide
- Rider unit test example
- Microsoft getting started guide
- Coverlet GitHub
- Cobertura documentation
- Cobertura github repo