.NET Learning Notes: Using xUnit + moq to unit test

References: https://xunit.net/docs/getting-started/v3/cmdline https://github.com/devlooped/moq 1.How to add a test project in your develop project(in the same solution) Project structure Solution/ | |--MyApp/ | MyApp.csproj | |--MyApp.Tests/ | MyApp.Tests.csproj | |-Solution.sln install xUnit: dotnet new install xunit.v3.templates create test project and reference to main project: botnet new unit -n MyApp.Tests dotnet sln add MyApp.Tests/MyApp.Tests.csproj cd MyApp.Tests dotnet add reference ../MyApp/MyApp.csproj 2.How to run your test .NET CLI(auto compile project, run all[Fact] and [Theory]) //run all tests dotnet test //run one project and not run some of them dotnet test MyApp.Tests dotnet test --filter "FullyQualifiedName~CalculatorTests" if you use rider or other IDE, directly run each test click the arrow on the left of test cases. 3.install moq dotnet add package Moq --version 4.20.72 Now you can write your test cases. example: public class TaskCategoryServiceTests { private readonly TaskCategoryService _service; private readonly Mock _mockCategoryRepository; private readonly Mock _mockTaskRepository; public TaskCategoryServiceTests() { _mockCategoryRepository = new Mock(); _mockTaskRepository = new Mock(); _service = new TaskCategoryService(_mockCategoryRepository.Object, _mockTaskRepository.Object); } [Fact] public async Task CreateCategoryAsync_ReturenSuccess_WhenValidInput() { var name = "Dev"; var description = "Dev"; var userId = "1"; _mockCategoryRepository .Setup(r => r.AddCategoryAsync(It.IsAny())) .Returns(Task.FromResult(ApiResponseCode.CategoryCreateSuccess)); var result = await _service.CreateCategoryAsync(name, description, userId); Assert.Equal(ApiResponseCode.CategoryCreateSuccess, result); _mockCategoryRepository.Verify(r => r.AddCategoryAsync(It.IsAny()), Times.Once); } [Fact] public async Task CreateCategoryAsync_ReturenServerError_WhenRepositoryThrowsException() { var name = "Dev"; var description = "Dev"; var userId = "1"; _mockCategoryRepository .Setup(r => r.AddCategoryAsync(It.IsAny())) .Returns(Task.FromResult(ApiResponseCode.CategoryCreateFailed)); var result = await _service.CreateCategoryAsync(name, description, userId); Assert.Equal(ApiResponseCode.CategoryCreateFailed, result); _mockCategoryRepository.Verify(r => r.AddCategoryAsync(It.IsAny()), Times.Once); } } FAQ: Why I use mock.Setup().Returns(() => count) but not mock.Setup().Returns(count)? When using Moq, Returns(count) and Returns(() => count) serve different purposes. Returns(count) captures the value of count at the moment the setup is defined, meaning it always returns that fixed value, even if count changes later. On the other hand, Returns(() => count) uses a lambda expression to defer evaluation until the method is actually called, so it returns the current value of count at runtime. This is useful when the value is expected to change during the test and you want the mock to reflect the most up-to-date value each time it's invoked. Should I use mock.Setup(foo => foo.DoSomethingAsync().Result).Returns(true) or mock.Setup(foo => foo.DoSomethingAsync()).ReturnsAsync(true)? You should prefer using mock.Setup(foo => foo.DoSomethingAsync()).ReturnsAsync(true); instead of accessing .Result on the async method. Using .ReturnsAsync(true) is the correct and safe way to mock asynchronous methods in Moq, as it properly returns a Task that resolves to the expected value. In contrast, using .Result forces synchronous execution of the task, which can lead to deadlocks, especially in environments like ASP.NET or test runners that manage synchronization contexts. ReturnsAsync is cleaner, more readable, and designed specifically for mocking Task-returning methods.

Apr 23, 2025 - 04:29
 0
.NET Learning Notes: Using xUnit + moq to unit test

References:
https://xunit.net/docs/getting-started/v3/cmdline
https://github.com/devlooped/moq

1.How to add a test project in your develop project(in the same solution)

Project structure
Solution/
|
|--MyApp/
|  MyApp.csproj
|
|--MyApp.Tests/
|  MyApp.Tests.csproj
|
|-Solution.sln

install xUnit:

dotnet new install xunit.v3.templates

create test project and reference to main project:

botnet new unit -n MyApp.Tests
dotnet sln add MyApp.Tests/MyApp.Tests.csproj
cd MyApp.Tests
dotnet add reference ../MyApp/MyApp.csproj

2.How to run your test
.NET CLI(auto compile project, run all[Fact] and [Theory])

//run all tests
dotnet test

//run one project and not run some of them
dotnet test MyApp.Tests
dotnet test --filter "FullyQualifiedName~CalculatorTests"

if you use rider or other IDE, directly run each test click the arrow on the left of test cases.

3.install moq

dotnet add package Moq --version 4.20.72

Now you can write your test cases.
example:

public class TaskCategoryServiceTests
{
    private readonly TaskCategoryService _service;
    private readonly Mock _mockCategoryRepository;
    private readonly Mock _mockTaskRepository;

    public TaskCategoryServiceTests()
    {
        _mockCategoryRepository = new Mock();
        _mockTaskRepository = new Mock();
        _service = new TaskCategoryService(_mockCategoryRepository.Object, _mockTaskRepository.Object);
    }

    [Fact]
    public async Task CreateCategoryAsync_ReturenSuccess_WhenValidInput()
    {
        var name = "Dev";
        var description = "Dev";
        var userId = "1";

        _mockCategoryRepository
            .Setup(r => r.AddCategoryAsync(It.IsAny()))
            .Returns(Task.FromResult(ApiResponseCode.CategoryCreateSuccess));

        var result = await _service.CreateCategoryAsync(name, description, userId);
        Assert.Equal(ApiResponseCode.CategoryCreateSuccess, result);
        _mockCategoryRepository.Verify(r => r.AddCategoryAsync(It.IsAny()), Times.Once);
    }

    [Fact]
    public async Task CreateCategoryAsync_ReturenServerError_WhenRepositoryThrowsException()
    {
        var name = "Dev";
        var description = "Dev";
        var userId = "1";
        _mockCategoryRepository
            .Setup(r => r.AddCategoryAsync(It.IsAny()))
            .Returns(Task.FromResult(ApiResponseCode.CategoryCreateFailed));

        var result = await _service.CreateCategoryAsync(name, description, userId);
        Assert.Equal(ApiResponseCode.CategoryCreateFailed, result);
        _mockCategoryRepository.Verify(r => r.AddCategoryAsync(It.IsAny()), Times.Once);
    }
}

FAQ:
Why I use mock.Setup().Returns(() => count) but not mock.Setup().Returns(count)?

When using Moq, Returns(count) and Returns(() => count) serve different purposes. Returns(count) captures the value of count at the moment the setup is defined, meaning it always returns that fixed value, even if count changes later. On the other hand, Returns(() => count) uses a lambda expression to defer evaluation until the method is actually called, so it returns the current value of count at runtime. This is useful when the value is expected to change during the test and you want the mock to reflect the most up-to-date value each time it's invoked.

Should I use mock.Setup(foo => foo.DoSomethingAsync().Result).Returns(true) or mock.Setup(foo => foo.DoSomethingAsync()).ReturnsAsync(true)?

You should prefer using mock.Setup(foo => foo.DoSomethingAsync()).ReturnsAsync(true); instead of accessing .Result on the async method. Using .ReturnsAsync(true) is the correct and safe way to mock asynchronous methods in Moq, as it properly returns a Task that resolves to the expected value. In contrast, using .Result forces synchronous execution of the task, which can lead to deadlocks, especially in environments like ASP.NET or test runners that manage synchronization contexts. ReturnsAsync is cleaner, more readable, and designed specifically for mocking Task-returning methods.