Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

C# xUnit + Moq によるユニットテストの実装例

0
Posted at

はじめに

xUnit + Moq でユニットテストを実装する備忘録です。

環境

  • Visual Studio Community 2022
  • .NET 8.0
  • xunit.v3 3.2.2
  • xunit.runner.visualstudio 3.1.5
  • coverlet.collector 10.0.0
  • Moq 4.20.72

ユニットテストの対象クラス

ユーザー ID による検索処理を行える、トランザクションスクリプトなサービスクラスです。他の CRUD 処理はノイズになりそうだったので、省略しています。

UserUseCase.cs
namespace TransactionScriptExampleNet8.Core;

/// <summary>
/// ユーザー関連のビジネスロジックを提供するインターフェースです。
/// </summary>
public interface IUserUseCase
{
    /// <summary>
    /// 指定されたユーザー ID に対応するユーザー情報を取得します。
    /// </summary>
    /// <param name="userId">ユーザー ID。</param>
    /// <returns><see cref="UserDto"/> オブジェクト。</returns>
    UserDto? GetUserById(string userId);
}

/// <summary>
/// ユーザー関連のビジネスロジックを提供します。
/// </summary>
public class UserUseCase : IUserUseCase
{
    private readonly IDbSessionFactory _dbSessionFactory;

    /// <summary>
    /// <see cref="UserUseCase"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    public UserUseCase(IDbSessionFactory dbSessionFactory)
    {
        _dbSessionFactory = dbSessionFactory;
    }

    /// <inheritdoc/>
    public UserDto? GetUserById(string userId)
    {
        // DB 接続は Dapper などを想定しています。
        using (IDbSession dbSession = _dbSessionFactory.CreateDbSession())
        {
            var sql = "SELECT * FROM Users WHERE user_id = @UserId";
            return dbSession
                .Query<UserDto>(sql, new { UserId = userId })
                .FirstOrDefault();
        }
    }
}

ユニットテストの実装例

例として正常系のテストパターンを実装しています。テストプロジェクトでは、サマリーはつけず、メソッド名でテスト内容を表現することがお作法らしいです。

UserUseCaseTests.cs
using Moq;
using TransactionScriptExampleNet8.Core;

namespace TransactionScriptExampleNet8.Test;

public class UserUseCaseTests
{
    private readonly Mock<IDbSessionFactory> _factoryMock;
    private readonly Mock<IDbSession> _dbSessionMock;
    private readonly UserUseCase _useCase;

    public UserUseCaseTests()
    {
        _factoryMock = new Mock<IDbSessionFactory>();
        _dbSessionMock = new Mock<IDbSession>();

        // CreateDbSession() が呼ばれたときに Moq で生成した
        // IDbSession の偽物のインスタンスを返すように設定する。
        _factoryMock
            .Setup(x => x.CreateDbSession())
            .Returns(_dbSessionMock.Object);

        // UserUseCase に Moq 化した IDbSessionFactory を注入する。
        _useCase = new UserUseCase(_factoryMock.Object);
    }

    [Fact]
    public void GetUserById_UserExists_ReturnsUser()
    {
        // --------------------------------------------------------------------
        // Arrange : 準備
        // --------------------------------------------------------------------

        // 戻り値として返す UserDto を作成する。
        var user = new UserDto
        {
            UserId = "001",
            Password = "P@ssw0rd",
            UserName = "Taro",
            UserGroup = "Admin",
            Remarks = "test"
        };

        // IDbSession.Query<T>() が呼ばれたときに
        // UserDto を返すように設定する。
        _dbSessionMock
            .Setup(x => x.Query<UserDto>(
                It.IsAny<string>(),
                It.IsAny<object>()))
            .Returns([user]);

        // --------------------------------------------------------------------
        // Act : 実行
        // --------------------------------------------------------------------

        // GetUserById() を呼び出す。
        UserDto? result = _useCase.GetUserById("001");

        // --------------------------------------------------------------------
        // Assert : 結果の検証
        // --------------------------------------------------------------------

        // GetUserById() の戻り値を検証する。
        Assert.NotNull(result);
        Assert.Equal("001", result!.UserId);

        // --------------------------------------------------------------------
        // Verify : 内部処理の検証
        // --------------------------------------------------------------------

        // GetUserById() の内部で IDbSessionFactory.CreateDbSession() が
        // 1 回だけ呼び出されたことを検証する。
        _factoryMock
            .Verify(
                x => x.CreateDbSession(),
                Times.Once);

        // GetUserById() の内部で IDbSession.Query<T>() が
        // 1 回だけ呼び出されたことを検証する。
        _dbSessionMock
            .Verify(
                x => x.Query<UserDto>(
                    It.IsAny<string>(),
                    It.IsAny<object>()),
                Times.Once);

        // GetUserById() の内部で IDisposable.Dispose() が
        // 1 回だけ呼び出されたことを検証する。
        _dbSessionMock
            .Verify(
                x => x.Dispose(),
                Times.Once);
    }
}

参考

おわりに

正直なところ、ただの CRUD 処理には過剰設計になると思います。複雑な計算がある時など、ちょうどよい塩梅の粒度で実装したいです。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?