Bob Code · Follow
25 min read · Apr 11, 2024
--
You just finished coding your new feature, it’s great and it works!
But how do you prove that it actually works?
Time to write your unit tests!
I. Introduction
- What are unit tests in .Net?
- Why do we have to create unit tests?
- What unit tests tools are there in .Net?
II. Getting Started
- Creating a xUnit Test project
- NuGet Package
- Why do unit test classes need to be public?
- Test Method Naming Convention
- What does the [Fact] attribute do?
- How to build xUnit test methods?
III. xUnit Data Annotations and Attributes
- Data Driven Tests with [Theory]
- [Trait]
IV. Mock
- Mock behaviour
V. AAA: Arrange
- SetUp
- Return
- Exceptions
- Implicit checks
VI. AAA: Act
- Fixture
VII. AAA: Assert
VIII. Running Test
- Manually
- Automatically
- Code Coverage
- Debugging
- Running Tests in Parallel
IX. Best Practices
X. Documentation & Error List
What are unit tests in .Net?
Unit tests refer to testing “unit of works”, which basically means:
- Code that has no external dependencies (isolated)
Unit testing is a software testing methodology where individual units or components of a software application are tested in isolation to ensure they function correctly.
In comparison integration test 2 or more components that interact together (not isolated).
End to End Test go even further and test an app through its UI# with tools like Selenium
Why do we have to create Unit tests?
Like do we really need to go through this? If it works with manual testing, then why even write unit tests?
Are there any developers who actually enjoy writing these things?
Isn’t it why we have testers?
Prove that it works
Sure, you can go debug mode, input some fake data and do a demo to the whole team to prove that it works.
But that’s not very time efficient, persuasive and a long term solution.
Isn’t it convenient to have all these passing tests showing to yourself and everyone else that the code works?
Regression
Basically, not only do you want to make sure that your current code works but also, your app is quite likely to change overtime and therefore you want to make sure that its other functionalities keep working as expected.
So that if you add or change code, the tests will always check whether the other functionalities you created before still work as they should.
Automation
You don’t want to manually have to manual test all the the time. That’s when tests come in handy, especially when automatically run in a pipeline!
What unit tests tools are there in .Net?
- xUnit
xUnit is a free, open-source, community-focused unit testing tool for .NET.
Its frameworks provide a framework for writing and running automated unit tests.
It is a project of the .NET Foundation. xUnit.net is the latest technology for unit testing .NET apps. It is free and open source.
xUnit is now the Microsoft default and standard testing tool in Visual Studio for .Net Core.
A nice feature of xUnit is the ability to test code from
- .Net Framework
- .Net Core
- Xamarin
This blog focuses on xUnit :)
- N Unit
NUnit is a unit-testing framework for all .NET languages
- MSTest
MSTest is the Microsoft test framework for all .NET languages
Now that we understand the key concepts, let’s create a xUnit test project!
Creating a xUnit Test project
Naming the test project:
Name of project + .UnitTests
Keep the global using.cs that will ensure that Xunit is used everywhere
NuGet Package
If you create a xUnit project from VS, the package should already be added, otherwise you can manually add it
And you will need this package to run tests in VS: xunit.runner.visualstudio
Naming the file:
Name of cs file to be tested + Tests, e.g. BrandControllerTests
Why do unit test classes need to be public?
In C#, unit tests typically need to be declared as public because they need to be accessible by the testing framework.
When you write unit tests using frameworks like NUnit, MSTest, xUnit, etc., these frameworks require that the test methods be public so that they can be discovered and executed.
Test Method Naming Convention
First thing first, a test method needs to be named right, it should consist of three parts:
- The name of the method being tested.
- The scenario under which it’s being tested.
- The expected behaviour when the scenario is invoked.
nameOfMethodBeingTested_Scenario_ExpectedBehaviour()
example, original method to be tested
public async Task<ActionResult<IEnumerable<Brand_DTO>>> GetAllBrands()
{
}
Test Method (add [Fact] on top)
[Fact]
public async Task GetAllBrands_ActionExecutes_CheckResultType_ReturnsBrand_DTOs()
{
}
Another naming convention is to use
<MethodName>_should_<expectation>_when_<condition>
Example: Constructor_should_throw_when_parameters_are_null
What does the [Fact] attribute do?
- [Fact]
The [Fact] attribute declares a test method that’s run by the test runner.
Therefore it must be added on top of each test method like below
[Fact] // => [fact] will make sure that the test is picked up by the test runner
public async Task GetAllBrands_ActionExecutes_CheckResultType_ReturnsBrand_DTOs()
{
}
How to build xUnit tests? Using Moq and AAA
There are basically 4 steps into building a test:
- 1/ Mock external dependencies
- 2/ Arrange
- 3/ Act
- 4/ Assert
1/ Mocking
So how to deal with dependencies? Well, we don’t! We simply simulate them by using a framework called Mock
First, you will need to install the package Moq
So what does Mocking do?
Let’s look at the below class
public class BrandController : ControllerBase
{
private readonly IBrandService _service; public BrandController(IBrandService service, ILogger<BrandController> logger)
{
_service = service;
}
You can see that it takes one external dependencies
- _service
However, a unit test should not rely on external objects.
Therefore we need to stimulate the behaviour of this object.
To do so, we will mock it, like so:
public class BrandControllerTests
{
private readonly Mock<IBrandService> _serviceMock; public BrandControllerTests()
{
_serviceMock = new Mock<IBrandService>(MockBehavior.Strict);
}
Now what we have our simulated object, so we can use it and its methods in our test code.
This is the method we want to test
public async Task<ActionResult<Brand_DTO>> GetBrandByID(int id)
{
return Ok(await _service.GetByIdAsync(id));
}
The method GetBrandByID in the BrandController uses the _service (that we just mocked) that:
- takes as input an int
- returns an object
GetBrandByID then returns an OkResult
2/ Arrange takes care of arranging the Mock object so it
- calls the method passed in the class we are testing, using SetUp
- returns the right object using Return
// Arrange
_serviceMock.Setup(x => x.GetByIdAsync(_id))
.ReturnsAsync(new Brand_DTO { ID = _id });
3/ Act will actually call the method we are testing passing the mock object(s) to the class under test
// Act
var controller = new BrandController(_serviceMock.Object);
var result = await controller.GetBrandByID(_id);
4/ Assert is when we check whether the test returns the expected value, in our test example we expect:
- An OkResult
- That it returns a object of type Brand_DTO
- That the integer id we pass as parameter is correct
// Assert
var okResult = Assert.IsType<OkObjectResult>(result.Result);
var item = Assert.IsType<Brand_DTO>(okResult.Value);
Assert.Equal(_id, item.ID);
And here is our complete xUnit test class!
public class BrandControllerTests
{
private readonly Mock<IBrandService> _serviceMock; public BrandControllerTests()
{
_serviceMock = new Mock<IBrandService>(MockBehavior.Strict);
}
[Fact]
public async Task GetByID_Returns_SingleObject()
{
// Arrange
_serviceMock.Setup(x => x.GetByIdAsync(_id))
.ReturnsAsync(new ProductModelInClubCollection_DTO { ID = _id });
// Act
var controller = new ProductModelInClubCollectionController(_serviceMock.Object, _loggerMock.Object, _mapperMock.Object);
var result = await controller.GetByID(_id);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result.Result);
var item = Assert.IsType<ProductModelInClubCollection_DTO>(okResult.Value);
Assert.Equal(_id, item.ID);
}
And that’s it!
Now let’s delve deeper into each part of xUnit and look at multiple scenarios
Data Driven Tests with [Theory]
Theory enables you to add multiple inputs to the test one method.
This is called Data Driven Tests
So in the below method we are passing 3 inputs to one test method
[Theory]
[InlineData(-1)]
[InlineData(0)]
[InlineData(1)]
public void IsPrime_ValuesLessThan2_ReturnFalse(int value)
{
var result = _primeService.IsPrime(value);
Assert.False(result, $"{value} should not be prime");
}
The limitation of [InlineData] is that we can only apply it to one test method.
If you wish to share input data amongst classes use the [ClassData] attribute instead
[Theory]
[ClassData(typeof(Input))]
public void IsPrime_ValuesLessThan2_ReturnFalse(int value)
{
var result = _primeService.IsPrime(value);
Assert.False(result, $"{value} should not be prime");
}
OR [MemberData]
[Theory]
[MemberData(nameof(Input)]
public void IsPrime_ValuesLessThan2_ReturnFalse(int value)
{
var result = _primeService.IsPrime(value);
Assert.False(result, $"{value} should not be prime");
}
For this you will need another class
public class Input: IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] { 1, 2, 3 };
} IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Alternatively you can also create your own attribute
public class Input: DataAttribute
{
public override IEnumerator<object[]> GetData(MethodInfo testMethod)
{
yield return new object[] { 1, 2, 3 };
}
}
Now we can use this custom attribute in our test
[Theory]
[Input]
public void IsPrime_ValuesLessThan2_ReturnFalse(int value)
{
var result = _primeService.IsPrime(value);
Assert.False(result, $"{value} should not be prime");
}
These techniques might seem cumbersome, thankfully there is another option: TheoryData
First we use ClassData
[Theory]
[ClassData(typeof(Input))]
public void IsPrime_ValuesLessThan2_ReturnFalse(int value)
{
var result = _primeService.IsPrime(value);
Assert.False(result, $"{value} should not be prime");
}
Then we create a TheoryData
class that’s way easier to set-up
public class Input: TheoryData
{
public Input()
{
Add(-1);
Add(0);
Add(3);
}
}
[Trait]
Useful for filtering and marking the type or group of tests, here:
- The trait type is category
- the category name is integration
[Fact, Trait("Category", "Integration")]
public async Task SendInactiveMember_Should_CreateTicket()
{
}
This is particularly useful to differenciate between test types (unit, integration, UI…)
[Fact]
[Trait("Category","Unit")]
public void Test1(){
...}[Fact]
[Trait("Category","Integration")]
public void Test2(){
...}
[Fact]
[Trait("Category","UI")]
public void Test3(){
...}
When would that be useful? Well, in pipelines for example!
You can now filter tests (credits: dateo-software.de)
# Run all unit tests
dotnet test --filter "(FullyQualifiedName!=Integration.Tests)&(FullyQualifiedName!=System.Tests)"# Run all integration tests
dotnet test --filter "(FullyQualifiedName=Integration.Tests)&(Category=ReadyForProduction)"
Mock behaviour
When creating Mocks, sometimes you want to make sure that the setup is exactly as it should, otherwise an error or exception should be thrown.
That’s when you want to use MockBehavior.Strict
If not and it doesn’t really matter whether the object behaves exactly as in real-life, such as with ILogger, then you can either keep as default or explicitly tag it as MockBehavior.Loose (the default behaviour)
public class BrandControllerTests
{
private readonly Mock<IBrandService> _serviceMock;
private readonly Mock<ILogger<BrandController>> _loggerMock;public BrandControllerTests()
{
_serviceMock = new Mock<IBrandService>(MockBehavior.Strict);
_loggerMock = new Mock<ILogger<BrandController>>(MockBehavior.Loose);
}
Strict
With strict mocks, the mock object will strictly enforce that only the expected interactions are allowed. Any unexpected calls to methods that haven’t been explicitly set up will result in a test failure.
Loose
Loose mocks, on the other hand, allow unexpected method calls to pass without failing the test. They are more permissive and lenient compared to strict mocks.
Remember every external dependencies Mock object?
If any is passed in your original method, you will need to arrange them, aka set them up.
The SetUp part is perhaps the most important set in your whole test! If you don’t exactly (like exactly) simulate the original method, with the exact same dependencies and return type, you will get this error:
Moq.MockException : invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.
Let’s look at this example, this is the original method
public async Task<ActionResult<ProductModelInClubCollection_DTO>> GetByID(int id)
{
return Ok(await _service.GetByIdAsync(id));
}
You can see that it uses one object:
- _service
The test method will need the object to be mocked, i.e. simulated
So we need to simulate the _service object to return a ClubCollectionGetModel
SetUp
Let’s set it up!
// Arrange
_serviceMock.Setup(x => x.GetByIdAsync(It.Is<int>()))
.ReturnsAsync(new ProductModelInClubCollection_DTO { ID = _id });
This is how we create a setup:
_objectMocked .SetUp
- (x => x.MethodUsed (Optional: the argument or type passed to the MethodUsed (e.g. It.Is<int>, or simply 1)))
- .Return (the object or type want to be returned)
Here are some examples visualised
See all SetUp Options in the Moq documentation below
Let’s look at some parameter examples that you can pass in the SetUp Method being mocked:
- It.IsAny<int>()
- 1
- It.Is<int>()
- It.Is<int>(i => i % 2 == 0)
- It.IsInRange<int>(0, 10, Range.Inclusive)
- It.IsRegex(“[a-d]+”, RegexOptions.IgnoreCase)
Here’s a compiled list of several setup and return options!
// Any integer, any value returned
_serviceMock.Setup(x => x.GetByIdAsync(It.IsAny<int>()))
.Returns(objecttobereturned)// hardcoded value
_serviceMock.Setup(x => x.GetByIdAsync(1))
.Returns(objecttobereturned)
// When checking for one specific value to be returned
_serviceMock.Setup(x => x.GetByIdAsync(It.Is<int>())
.Returns(objecttobereturned)
// Can include specific matching pattern
_serviceMock.Setup(x => x.GetByIdAsync(It.Is<int>(i => i % 2 == 0)))
.Returns(objecttobereturned)
// Matching ranges
_serviceMock.Setup(x => x.GetByIdAsync(It.IsInRange<int>(0, 10, Range.Inclusive)))
.Returns(objecttobereturned)
// Matching regex
_serviceMock.Setup(x => x.GetByIdAsync(It.IsRegex("[a-d]+", RegexOptions.IgnoreCase)))
.Returns(objecttobereturned)
// Matching complex condition
_serviceMock.Setup(x => x.Method(It.Is<int>(i => i % 2 == 0 && i > 0)))
.Returns(objectToBeReturned);
// Callback example
_serviceMock.Setup(x => x.Method(It.IsAny<int>()))
.Callback<int>(value => Console.WriteLine($"Called with value: {value}"));
// Throws exception
_serviceMock.Setup(x => x.Method(It.IsAny<string>()))
.Throws<Exception>();
// Throws exception with custom message
_serviceMock.Setup(x => x.Method(It.IsAny<string>()))
.Throws(new Exception("Custom exception message"));
// Sets up multiple return values, will return 'result1' on the first call, and 'result2' on subsequent calls
_serviceMock.SetupSequence(x => x.Method(It.IsAny<int>()))
.Returns("result1").Returns("result2");
// Setup with multiple arguments
_serviceMock.Setup(x => x.MethodWithMultipleArgs(It.IsAny<int>(), It.IsAny<string>()))
.Returns(objectToBeReturned);
// Setup with multiple arguments and specific conditions
_serviceMock.Setup(x => x.MethodWithMultipleArgs(It.Is<int>(i => i > 0), It.Is<string>(s => s.Length > 5)))
.Returns(objectToBeReturned);
// Setup with multiple arguments and specific conditions for one argument and any value for the other
_serviceMock.Setup(x => x.MethodWithMultipleArgs(It.Is<int>(i => i > 0), It.IsAny<string>()))
.Returns(objectToBeReturned);
// Setup with custom matching using Match<T> class
_serviceMock.Setup(x => x.MethodWithCustomMatcher(Match.Create<int>(i => i % 2 == 0)))
.Returns(objectToBeReturned);
// Setup with custom return value based on argument values
_serviceMock.Setup(x => x.MethodWithCustomReturnValue(It.IsAny<int>()))
.Returns((int arg) => arg % 2 == 0 ? "Even" : "Odd");
// Setup with asynchronous return value using Task.FromResult
_serviceMock.Setup(x => x.AsyncMethod())
.ReturnsAsync(Task.FromResult(objectToBeReturned));
// Setup with asynchronous return value using Task.FromResult with delay
_serviceMock.Setup(x => x.AsyncMethodWithDelay()).ReturnsAsync(async () =>
{
await Task.Delay(100);
return objectToBeReturned;
});
// Setup with asynchronous return value using async lambda
_serviceMock.Setup(async x => await x.AsyncMethod())
.Returns(objectToBeReturned);
// Setup with throwing an exception asynchronously
_serviceMock.Setup(x => x.AsyncMethod())
.ThrowsAsync(new Exception("Async exception"));
// Setup with custom behavior using a function
_serviceMock.Setup(x => x.MethodWithCustomBehavior(It.IsAny<int>()))
.Returns((int arg) =>
{
if (arg < 0)
throw new ArgumentException("Argument cannot be negative");
return objectToBeReturned;
});
Return
Returns or ReturnsAsync and The Object(s) you want to return
// Returns
_serviceMock.Setup(x => x.GetByIdAsync(1).Returns(objecttobereturned)// Returns Async
_serviceMock.Setup(x => x.GetByIdAsync(_id)).ReturnsAsync(objecttobereturned)
// Or use Task.FromResult instead of ReturnsAsync
_serviceMock.Setup(x => x.GetByIdAsync(1).Returns(Task.FromResult(objecttobereturned))
Return for void/task
What about methods that actually don’t return anything?
See this example, we actually just perform a Task and only return an Ok (ActionResult), but the _service doesn’t return anything
public async Task<IActionResult> Delete(int id)
{
await _service.DeleteAsync(id);
return Ok();
}
In this case we just return Task.CompletedTask
// For tasks: Task.CompletedTask
_serviceMock.Setup(x => x.DeleteAsync(_id)).Returns(Task.CompletedTask);// For void: Callback
_serviceMock.Setup(x => x.DeleteAsync(_id)).Callback(() => { });
Return for null
What about methods that should return null (e.g. for testing null exceptions)?
// null string
mock.Setup(p => p.Property).Returns(null as string);// null object
_mock.Setup(p => p.GetProductmodel(id).ReturnsAsync((ProductmodelResponseModel)null);
Throwing exception
Sometime a method block throws an exception, so instead of returns we use .Throws
mock.Setup(p => p.DoSomething())
.Throws(new Exception("My custom exception"));// Async
mock.Setup(p => p.DoSomething())
.ThrowsAsync(new Exception("My custom exception"));
Using methods from the base class
What if your class uses a base, like so
public class BrandInClubAssortmentService : GenericService<BrandInClubAssortment, BrandInClubAssortment_DTO>, IBrandInClubAssortmentService
{
private readonly IProductAndPricingAPIClient _productAndPricingAPIClient; public BrandInClubAssortmentService(
IProductAndPricingAPIClient productAndPricingAPIClient,
IMapper mapper)
: base(mapper)
{
_productAndPricingAPIClient = productAndPricingAPIClient;
}
You’d need to Mock the mapper and logger to ensure good functioning of the class, and also pass them as Mock.Object in your class constructor.
var service = new BrandInClubAssortmentService(
ProductAndPricingAPIClientMock.Object,
MapperMock.Object,
);
Otherwise you are quite likely to encounter the below exception
Moq.MockException : IMapperBase.Map<BrandInClubAssortment>(BrandInClubAssortment_DTO) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
What if, even though you have set-up the base mock objects correctly, you still get this error?
If you look at the documentation, then you will see the recommendation to use .CallBase()
mock.Setup(p => p.Greet()).CallBase();
However, this only works if the mock is created from a class, not an interface, and the method being called is not abstract. So, this won’t work
_fixture.MapperMock.Setup(x => x.Map<BrandInClubAssortment>(It.IsAny<BrandInClubAssortment_DTO>())).CallBase();
If the Mock is as follows
MapperMock = new Mock<IMapper>(MockBehavior.Strict);
The solution is to set it up as a normal mock
_fixture.MapperMock.Setup(x => x.Map<BrandInClubAssortment>(It.IsAny<BrandInClubAssortment_DTO>()))
.Returns(new BrandInClubAssortment());
Implicit checks
The above is for explicit verification aka, we pass a method and expect a hardcoded object, the other way of doing this is just checking whether the method works regardless of what it returns using:
- Verifiable
- VerifyAll
// Implicit uses .Verifiable
_serviceMock.Setup(p => p.GetByIdAsync(It.IsAny<string>())).Verifiable();// In the Assert part we use
_serviceMock.VerifyAll();
// OR to check multiple mocks
Mock.Verify(_serviceMock1, _serviceMock2);
Once you have all dependencies setup, now it’s time to actually instantiate your class that you are testing
The trick is to pass the objects of the Mocks instead of the external dependencies to your class.
// Act# First we instantiate the class with Mock objects
var controller = new ProductModelInClubCollectionController(_serviceMock.Object);
# Then we actually call the method we want to test
var result = await controller.GetAll();
Finally, it’s time to check if the method returns what we expect, we do this in the Assert
Fixture
What is the IFixture class in c# and how to use it with xUnit?
Since you will most likely end-up with a lot of tests, instantiating a new object for each test method might not be the best in terms of resources.
One potential solution would be to instantiate your object in the constructor
public class ProductModelInClubCollectionServiceTests
{
private readonly Mock<IGenericRepository<ClubCollection>> _ccRepositoryMock; private readonly ProductModelInClubCollectionService _service;
public ProductModelInClubCollectionServiceTests()
{
_ccRepositoryMock = new Mock<IGenericRepository<ClubCollection>>(MockBehavior.Strict);
_service = new ProductModelInClubCollectionService(_ccRepositoryMock.ObjectcRepositoryMock.Object);
}
Even if you pass the object (example above “controller”) in the constructor of the test class, which will help in terms of code size, the amount of objects created will be the same.
This calls for the need of another solution, which is the fixture!
However, most documentation provides examples that don’t include any external objects like so
public class StackTests : IDisposable
{
Stack<int> stack; public StackTests()
{
stack = new Stack<int>();
}
public void Dispose()
{
stack.Dispose();
}
[Fact]
public void WithNoItems_CountShouldReturnZero()
{
var count = stack.Count;
Assert.Equal(0, count);
}
In real life scenarios you’re quite likely to have several mock.Object within your constructor!
So let’s see how to go about it :)
The trick is to add the Mocks in the Fixture
public class BrandInClubAssortmentServiceFixture : IDisposable
{
public Mock<IGenericRepository<ClubCollection>> ClubCollectionRepositoryMock { get; } public BrandInClubAssortmentService brandInClubAssortmentFixture { get; }
public BrandInClubAssortmentServiceFixture()
{
ClubCollectionRepositoryMock = new Mock<IGenericRepository<ClubCollection>>(MockBehavior.Strict);
var service = new BrandInClubAssortmentService(
ClubCollectionRepositoryMock.Object,
);
brandInClubAssortmentFixture = service;
}
public void Dispose()
{
}
}
Yes as you can see above you need to add all the mock manually, you might think that it is duplicating the original Mock in your test class but let’s see what comes next ;)
Let’s consider the original code
[Fact]
public async Task AddAsync_WhenBrandDoesNotExist_ThrowsPlutusBusinessRuleViolationException()
{
// Arrange
var service = new BrandInClubAssortmentService(_ccRepositoryMock.Object);
var dto = new BrandInClubAssortment_DTO { ClubCollectionID = 1, BrandName = "brand" };
_ccRepositoryMock.Setup(x => x.GetByIdAsync(It.IsAny<int>())).ReturnsAsync(new ClubCollection()); // Act
async Task Act() => await service.AddAsync(dto);
// Assert
await Assert.ThrowsAsync<PlutusBusinessRuleViolationException>(Act);
}
Now, we need to add the fixture to our code
[Fact]
public async Task AddAsync_WhenBrandDoesNotExist_ThrowsPlutusBusinessRuleViolationException()
{
// Arrange
var dto = new BrandInClubAssortment_DTO { ClubCollectionID = 1, BrandName = "brand" };
_fixture.ClubCollectionRepositoryMock.Setup(x => x.GetByIdAsync(It.IsAny<int>())).ReturnsAsync(new ClubCollection());
_fixture.ProductAndPricingAPIClientMock.Setup(x => x.GetBrand(It.IsAny<string>())).ReturnsAsync((BrandResponseModel)null); // Act
async Task Act() => await _fixture.brandInClubAssortmentFixture.AddAsync(dto);
// Assert
await Assert.ThrowsAsync<PlutusBusinessRuleViolationException>(Act);
}
We now set up the fixture Mocks and then we use the _fixture to directly use the method.
So in the test constructor, we actually don’t need any Mock anymore!
Initially we had this constructor for the test
public class BrandInClubAssortmentServiceTests : IClassFixture<BrandInClubAssortmentServiceTests.BrandInClubAssortmentServiceFixture>
{
private readonly Mock<IGenericRepository<ClubCollection>> _ccRepositoryMock; private readonly BrandInClubAssortmentServiceFixture _fixture;
public BrandInClubAssortmentServiceTests(BrandInClubAssortmentServiceFixture fixture)
{
_ccRepositoryMock = new Mock<IGenericRepository<ClubCollection>>(MockBehavior.Strict);
_fixture = fixture;
}
But now we can just keep the _fixture :)
public class BrandInClubAssortmentServiceTests : IClassFixture<BrandInClubAssortmentServiceTests.BrandInClubAssortmentServiceFixture>
{
private readonly BrandInClubAssortmentServiceFixture _fixture; public BrandInClubAssortmentServiceTests(BrandInClubAssortmentServiceFixture fixture)
{
_fixture = fixture;
}
Using Fixture amongst several classes
Now, let’s image that several of our classes actually use the _fixture.
What we would need to do is:
#1 Add the [Collection] attribute to all classes that use the _fixture
You can actually remove the following! : IClassFixture<BrandInClubAssortmentServiceTests.BrandInClubAssortmentServiceFixture> as [Collection] is now enough to use Fixture
[Collection("ServiceCollectionName")]
public class BrandInClubAssortmentServiceTests
{
private readonly BrandInClubAssortmentServiceFixture _fixture; public BrandInClubAssortmentServiceTests(BrandInClubAssortmentServiceFixture fixture)
{
_fixture = fixture;
}
#2 Create a collection class that
- Has the attribute[CollectionDefinition] with the matching name in the other classes
- Inherits from ICollectionFixture<TheNameOfYourFixture>
[CollectionDefinition("BrandInClubAssortmentServiceTests")]
public class BrandInClubAssortmentServiceCollection : ICollectionFixture<BrandInClubAssortmentServiceFixture>
{
}
Whether fixture is a feature you want to use is up to you.
I believe there are certain advantages of using the Fixture feature:
- Object Management:
xUnit.net creates a new instance of the test class for every test. When using a class fixture, xUnit.net will ensure that the fixture instance will be created before any of the tests have run, and once all the tests have finished, it will clean up the fixture object by calling Dispose
, if present.
- Code Reusability:
Set up your object once and reuse it everywhere in your test classes
In assert we check whether the test performs as it should.
This is a critical step as there are many things we can check, this where you want to cover all the paths possible.
Here are some examples
- Check whether the result returns anything (e.g. notnull or null)
- Check whether the result returns the right values, types and amount of values
- Check whether the methods used to come to the result have been called
Single Object
// Result
Assert.IsType<OkObjectResult>(result.Result);// Type
Assert.IsType<ProductModelInClubCollection_DTO>(okResult.Value);
Assert.IsType(typeof(testingclass), actualClass);
Assert.IsType<MyClass>(actualClass);
// Value: .Equal(expected, actual) OR .NotEqual(expected, actual)
Assert.Equal(_id, item.ID);
// Null/NotNull
Assert.NotNull(item.ID);
// False/True
Assert.False(string.IsNullOrEmpty(result.ExpectedString);
// Void
loginServiceMock.Verify(x => x.Logout(), Times.Once);
// Matches(expected, actual)
Assert.Matches("ExpectedString", result.ExpectedString);
Strings
// Specific assertion
Assert.Equal("<strong>abc</strong>", result, ignoreCase: true);// More general
Assert.StartsWith("<strong>", result, StringComparison.OrdinalIgnoreCase);
Assert.EndsWith("<strong>", result);
Assert.Contains("abc", result, StringComparison.OrdinalIgnoreCase);
List/IEnumerable
// Result Type
var okResult = Assert.IsType<OkObjectResult>(result.Result);// Values
var items = Assert.IsAssignableFrom<IEnumerable<ProductModelInClubCollection_DTO>>(okResult.Value);
// Count
Assert.Equal(3, items.Count());
// Properties
Assert.Equal(1, items.ElementAt(0).ID);
Assert.Equal(2, items.ElementAt(1).ID);
Assert.Equal(3, items.ElementAt(2).ID);
// Contains/ DoesNotContain
Assert.Contains(expectedItem, result);
// All
Assert.All(collection, item => Assert.NotNull(item))
// Equal
Assert.Equal(expectedCollection, actualCollection)
Range
// InRange
Assert.InRange(customer.Age, 25, 40)
Error Messages
// Bad Request
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.Equal("ProductModelId should be in the format '123456'", badRequestResult.Value);
Interface implementations
var items = Assert.IsAssignableFrom<IYourInterface>(okResult.Value);
Exceptions
// Throw exception
Assert.Throws<DivideByZeroException>(result);Assert.Throws<ArgumentException>( ()=> customer.GetOrdersByName(null));
// Check exception message
Assert.Equal("Hello", exception.Message);
Check whether mock methods were called, using VerifyAll() and VerifyNoOtherCalls()
_objectMock.VerifyAll();
_objectMock.VerifyNoOtherCalls();
_objectMock.VerifyAll()
This method verifies that all expected interactions with the mocked object have occurred.
In other words, it ensures that all the methods that were set up with expectations (such as Setup
calls in Moq) have been called during the test. If any of the expected interactions did not occur, this method typically throws an exception, indicating a test failure.
2. _objectMock.VerifyNoOtherCalls()
This method verifies that there were no additional unexpected calls made to the mocked object beyond those explicitly set up with expectations.
This may sound irrelevant but it is not. Other calls can simply cause side effects which is often the main cause of production issues
It's used to ensure that the test is not calling any methods on the mocked object that it shouldn't be. If there are unexpected calls, this method also typically throws an exception, indicating a test failure.
Complete Assert List
using Xunit;
using System;public class MyTests
{
[Fact]
public void EqualityAssertions()
{
// Equality Assertions
Assert.Equal(expected, actual);
Assert.NotEqual(notExpected, actual);
Assert.StrictEqual(expected, actual);
Assert.NotStrictEqual(notExpected, actual);
}
[Fact]
public void NullityAssertions()
{
// Nullity Assertions
Assert.Null(obj);
Assert.NotNull(obj);
}
[Fact]
public void BooleanAssertions()
{
// Boolean Assertions
Assert.True(condition);
Assert.False(condition);
}
[Fact]
public void TypeAssertions()
{
// Type Assertions
Assert.IsType(expectedType, obj);
Assert.IsNotType(unexpectedType, obj);
Assert.IsAssignableFrom(baseType, obj);
}
[Fact]
public void ComparisonAssertions()
{
// Comparison Assertions
Assert.InRange(value, low, high);
Assert.NotInRange(value, low, high);
}
[Fact]
public void CollectionAssertions()
{
// Collection Assertions
Assert.Contains(expected, collection);
Assert.DoesNotContain(unexpected, collection);
Assert.Empty(collection);
Assert.NotEmpty(collection);
Assert.Single(collection);
Assert.All(collection, condition);
}
[Fact]
public void ExceptionAssertions()
{
// Exception Assertions
Assert.Throws<Exception>(() => SomeMethod());
Assert.ThrowsAny<Exception>(() => SomeMethod());
Assert.ThrowsAsync<Exception>(async () => await SomeMethodAsync());
Assert.ThrowsAnyAsync<Exception>(async () => await SomeMethodAsync());
Assert.DoesNotThrow(() => SomeMethod());
}
[Fact]
public void StringAssertions()
{
// String Assertions
Assert.StartsWith(expectedSubString, actual);
Assert.EndsWith(expectedSubString, actual);
Assert.Contains(subString, actual);
Assert.Matches(pattern, actual);
Assert.DoesNotMatch(pattern, actual);
}
[Fact]
public void MiscellaneousAssertions()
{
// Miscellaneous Assertions
Assert.Same(expected, actual);
Assert.NotSame(notExpected, actual);
Assert.Empty(strOrCollection);
Assert.NotEmpty(strOrCollection);
Assert.Equal<T>(expected, actual);
}
}
Understand how xUnit runs your test methods
Upon clicking run tests, xUnit will create an instance of the test class for every method test being executed.
Manually
Use the command line
dotnet test
Or right click your test class and click “run tests”
A nice GUI (Test explorer) with all your tests will appear
You can also access this GUI using the Test icon on the top in VS
Automatically with a pipeline
Now, once you have finished running your tests locally, you want to make sure that any time new code is being pushed, that the tests are always being run.
You will want to do this automatically, and this where pipelines shine!
I suggest you check my other blog on how to do that :)
What is code coverage?
So you’ve run all your tests and you think, job’s done!
Well, not so fast, first thing you want to ensure is that the tests cover most of your code.
This is referred to as code coverage.
A code coverage report will give you how much percent of your code has been tested, whilst 100% is nice, a typical project aims for 80% code coverage
Code Coverage is available through Coverlet
Coverlet is a free and open-source tool, to use it install the following SDK to your test project
It should come by default with your xUnit project
In the command line in your test project run
dotnet test /p:CollectCoverage=true
You will then receive the following report
Again this is manual and I recommend using the test coverage in a pipeline as mentioned above in my other blog!
Debugging Tests
Just as any normal code, one can actually debug your test, which is really useful to understand what input, argument or any setup that is missing.
You can do so manually by setting up a breakpoint and then selecting the Debug button
Note that you will get into the class that is being tested once you instantiate the class object with the Mock objects (in the ACT phase)
Press F11 and you will land in the tested class
Running Tests in Parallel
Please have a look at the section on Fixtures and Collections
xUnit assumes that every test class is its own category, in turns it runs each test category in parallel (i.e. using a diferrent thread).
That is unless we have the [Collection] attribute is applied to classes, which will run these tests in a sequential manner.
Understanding this, it is good practice to break-down tests into multiples test classes so that xUnit can treat them as separate categories and therefore run them in parallel.
However, if some tests should be run sequentially, then simply apply the [Collection] attribute.
As an example, Test1 and Test2 will run sequentially
public class TestClass1
{
[Fact]
public void Test1()
{
Thread.Sleep(3000);
} [Fact]
public void Test2()
{
Thread.Sleep(5000);
}
}
Test1 and Test2 will run in parallel
public class TestClass1
{
[Fact]
public void Test1()
{
Thread.Sleep(3000);
}
}public class TestClass2
{
[Fact]
public void Test2()
{
Thread.Sleep(5000);
}
}
Test1 and Test2 will run sequentially
[Collection("Our Test Collection #1")]
public class TestClass1
{
[Fact]
public void Test1()
{
Thread.Sleep(3000);
}
}[Collection("Our Test Collection #1")]
public class TestClass2
{
[Fact]
public void Test2()
{
Thread.Sleep(5000);
}
}
- Keep it simple
If your test takes more time than writing the feature, it means that perhaps the feature needs some refactoring.
It should test only one component or method, not more!
- Check all possible paths
Ensure that you have tested all possible inputs, exceptions and loops within the tested method.
Check happy path but also illegal arguments (i.e. nulls, out-of-range…)
- Use Mocking to remove external dependencies
You should not have any dependencies on external objects in your tests, use Mock to simulate them
- Fast: make use of parallelism
Projects will quickly add more and more unit tests, therefore they need to remain lightweight to run fast and not bug down the application.
One way of doing so is by creating more classes so xUnit can make use of parallelism
- Minimal
Don’t make too many assertions in one unit test, ideally you’d want one assert per test class
- Meet code coverage target
A typical project aims for 80% code coverage
- Use Fixture
Reuse your test object as much as possible using Fixtures
- Use Data-driven test
Create fewer tests and test all your path in one test
- Follow the AAA Model
Keep things easy to read, to recreate and to code!
Moq.MockException : IMapperBase.Map<BrandInClubAssortment>(BrandInClubAssortment_DTO) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
Solution
mapper needs to be mapped for both directions!
_fixture.MapperMock.Setup(x => x.Map<BrandInClubAssortment>(It.IsAny<BrandInClubAssortment_DTO>()))
.Returns((BrandInClubAssortment_DTO source) => new BrandInClubAssortment
{
ClubCollectionID = source.ClubCollectionID,
BrandName = source.BrandName,
ID = source.ID
});_fixture.MapperMock.Setup(x => x.Map<BrandInClubAssortment_DTO>(It.IsAny<BrandInClubAssortment>()))
.Returns((BrandInClubAssortment source) => new BrandInClubAssortment_DTO
{
ClubCollectionID = source.ClubCollectionID,
BrandName = source.BrandName,
ID = source.ID
});