https://unsplash.com/photos/EWLHA4T-mso

Unit test quick book C#, .Net Core, Moq, xUnit

Gibin Francis
4 min readFeb 23, 2023

Some quick notes about writing unit tests using C#, .Net Core, moq, xUnit.

You can refer to some best practices in link in reference section

Simple Test

This is a very basic test case sample

[Fact]
public void Add_EmptyString_ReturnsZero()
{
// Arrange
var stringCalculator = new StringCalculator();

// Act
var actual = stringCalculator.Add("");

// Assert
Assert.Equal(0, actual);
}

Show Name on Test

If you want to overwrite the test name with a meaningful formatted test, you can use the below

[Fact(DisplayName = "Metod or group : Should creitea with response")]

Mapper to Test

In most case you will be using a mapper inside your application and you may have initialized them inside your startup class, to assign them in your testing, you can use the same profile or a duplicated profile and assign them in your test. you can also use them in a separate test utility file so that you can re share the same between tests.

public static class TestUtils
{
public static Mapper GetMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile(typeof(MappingProfileConfiguration));
});
return new Mapper(config);
}
}

var mapper = TestUtils.GetMapper();

Mock Configuration to Test

In most cases we will be using configurations with in our applications, to test the same we can use the below mocked functionality to test based on your need

public static IConfiguration GetMockConfiguration()
{
var inMemorySettings = new Dictionary<string, string> {
{"SampleKey1", "SampleValue1"},
{"SampleKey2", "SampleValue2"}
};
return new ConfigurationBuilder()
.AddInMemoryCollection(inMemorySettings)
.Build();
}

Mocked HTTP Context

In Some cases you may need to use the HTTP Context values while testing, like a bearer token or something, for those kind of tests, you can use this mock functionality which will set a set of dummy key values to the HTTP Context and return the context for you test

public static HttpContext GetHttpContext(List<(string, string)> headerArray)
{

var claims = new List<Claim>();
foreach (var item in headerArray)
{
claims.Add(new Claim(item.Item1, item.Item2));
}

var identity = new ClaimsIdentity(claims, "TestAuthType");
var claimsPrincipal = new ClaimsPrincipal(identity);

return new DefaultHttpContext()
{
User = claimsPrincipal
};

}

you can set them like below

var mockContext = new Mock<IHttpContextAccessor>();
mockContext.SetupGet(x => x.HttpContext)
.Returns(TestUtils.GetHttpContext(
new List<(string, string)> {
("SampleKey1", "SampleValue1"),
("SampleKey2", "SampleValue2") }));

Mocking a repository with dummy value

mocking a simple repository with some dummy values

mockRepo.Setup(x => x.QuerySingleAsync(
It.IsAny<Expression<Func<yourDto, bool>>>()))
.ReturnsAsync(new yourDto
{
yourDto_Id = 20001,
yourDto_Prop1 = 18,
yourDto_Prop2 = 30
});

Mocking a repository with null value

mocking with a null value, to test the failure cases

yourDto nullDto = null;
mockRepo.Setup(x => x.QuerySingleAsync(
It.IsAny<Expression<Func<yourDto, bool>>>()))
.ReturnsAsync(nullDto);

Mocking a repository with multiple invocations

Incase of multiple invocations to the function you have to check the same in sequence like below

mockRepo.SetupSequence(x => x.QuerySingleAsync(
It.IsAny<Expression<Func<yourDto, bool>>>()))
.ReturnsAsync(new yourDto //first response
{
yourDto_Id = 20001,
yourDto_Prop1 = 18,
yourDto_Prop2 = 30
})
.ReturnsAsync(new yourDto //second respnse
{
yourDto_Id = 20001,
yourDto_Prop1 = 18,
yourDto_Prop2 = 30
});

Mocking Repository and check the received object

Incase of saving an object to repo, you may need to check the object while asserting the data, you can use below sample for the same

yourDto saveObject = null;
mockRepo.Setup(x => x.CreateAsync(It.IsAny<yourDto>()))
.Callback<yourDto>((obj) => saveObject = obj)
.ReturnsAsync((true, 1))
Assert.NotNull(saveObject);

Assert the mock service executed count

In some cases you may need to check the number of times the mocked services executed, for those you can use below

mockRepo.Verify(x => x.CreateAsync(It.IsAny<yourDto>()), Times.Once());

Mock Exceptions

In some cases you may need to test exceptions, use below snippet for the same

mockRepo.Setup(x => x.CreateAsync(It.IsAny<yourDto>()))
.Throws(new ArgumentNullException());

Test Multiple Inputs

[Theory]
[InlneData(1,2,3)]
[InlneData(2,2,4)]
public void Add_ShoulddWorkWithMultipleInputs(int item1, int item2, int output)
{
// Arrange
var stringCalculator = new StringCalculator();

// Act
var actual = stringCalculator.Add(item1, item2);

// Assert
Assert.Equal(output, actual);
}

Test Multiple Objects

previous example will show the testing for minimal number of parameters for unit testing, but if its an object with multiple properties, then it will be hard to provide them inline, for that we can use below

[Theory]
[MemberData(nameof(GetTestData))]
public void Add_ShoulddWorkWithMultipleInputs(youtDto)
{
// Arrange
var stringCalculator = new StringCalculator();

// Act
var actual = stringCalculator.Add(youtDto.Item1, youtDto.Item2);

// Assert
Assert.Equal(youtDto.Output, actual);
}

public static IEnumerable<youtDto> GetTestData()
{
yield return new youtDto { Item1 = 1, Item1 = 2, Output = 3};
yield return new youtDto { Item1 = 2, Item1 = 2, Output = 4};
}

Load IFormFile in testing

Loading file as part of testing, this will use some dummy files in your test file and load the same while testing

var stream = File.OpenRead(filePath);
IFormFile file = new FormFile(stream, 0, stream.Length, "files", Path.GetFileName(filePath))
{
Headers = new HeaderDictionary(),
ContentType = (filePath.EndsWith("jpg") || filePath.Split('.')[1] == "jpeg") ? "image/jpeg"
: filePath.EndsWith("png") ? "image/png"
: "image/bmp",
};

These are some of the useful snippets for your unit test, will keep updating with more, keep coding.. :)

Reference

--

--

Gibin Francis
Gibin Francis

Written by Gibin Francis

Technical guy interested in MIcrosoft Technologies, IoT, Azure, Docker, UI framework, and more

Responses (1)