yotiky Tech Blog

とあるエンジニアの備忘録

Unity Unit Test(単体テスト)入門 - Moq

目次

はじめに

  • この一連の記事は
    • UnityのUnity Test Frameworkを使ったテストに関して調べたメモ書きに補足を足したもの
    • Unityのテスト、およびDIコンテナ、モックライブラリの基本的な使い方やそれぞれの役割など入門レベルの解説
    • ライブラリのリファレンス的な使い方などについては公式や他の記事参照
    • 実機テストやCIは含まない
    • TDDに関しては考慮しない

シリーズの目次

環境

Moq

手順

基本的な使い方

  • テストのasmdefに、Moq.dllの参照を追加する
  • テストコードでMockを使ってテストする

  • インターフェース

public interface IShield
{
    string Name { get; }

    uint DefencePower { get; }

    void AddEffect(int value);

    uint CalcDefencePower();
}
  • テストコード
[TestFixture]
public class MoqUnitTest
{
    [Test]
    public void MockObjectからのOutputの確認()
    {
        var mock = new Mock<IShield>();
        
        // プロパティのGet
        mock.Setup(x => x.Name).Returns("Name");
        // メソッドの戻り値
        mock.Setup(x => x.CalcDefencePower()).Returns(100);

        IShield shield = mock.Object;

        Assert.That(shield.Name, Is.EqualTo("Name"));
        Assert.That(shield.CalcDefencePower(), Is.EqualTo(100));
    }

    [Test]
    public void MockObjectへのInputの確認()
    {
        var mock = new Mock<IShield>();

        mock.Object.AddEffect(-10);

        mock.Verify(x => x.AddEffect(-10), Times.Once);
    }

    [Test]
    public void Mockのコールバック()
    {
        LogAssert.Expect(LogType.Log, "AddEffect called.");

        var mock = new Mock<IShield>();
        mock.Setup(x => x.AddEffect(It.IsAny<int>()))
            .Callback<int>(x => Debug.Log($"AddEffect called."));

        mock.Object.AddEffect(0);

        mock.Verify(x => x.AddEffect(It.IsAny<int>()), Times.AtLeastOnce);
    }
}

サンプル

アプリ側

public interface ISystemClock
{
    DateTime Now { get; }
}

public class Ticket
{
    private ISystemClock _clock;
    private uint _expireDays;

    public Ticket(ISystemClock clock, uint expireDays)
    {
        _clock = clock;
        _expireDays = expireDays;
    }

    public DateTime Issue()
    {
        return _clock.Now.AddDays(_expireDays);
    }
}

with Extenject(Zenject)

[TestFixture]
public class MoqZenjectUnitTest : ZenjectUnitTestFixture
{
    [Test]
    public void SimpleTest()
    {
        var mock = new Mock<ISystemClock>();
        mock.Setup(x => x.Now).Returns(new DateTime(2023, 1, 1));

        Container.BindInstance(mock.Object)
            .AsTransient();

        Container.Bind<Ticket>()
            .AsTransient()
            .WithArguments(10u);

        var target = Container.Resolve<Ticket>();
        Assert.That(target.Issue, Is.EqualTo(new DateTime(2023, 1, 1).AddDays(10)));
    }
}

with VContainer

[TestFixture]
public class MoqVContainerTest
{
    [Test]
    public void SimpleTest()
    {
        var mock = new Mock<ISystemClock>();
        mock.Setup(x => x.Now).Returns(new DateTime(2023, 1, 1));

        var builder = new ContainerBuilder();

        builder.RegisterInstance(mock.Object);

        builder.Register<Ticket>(Lifetime.Transient)
            .WithParameter(typeof(uint), 10u);

        var container = builder.Build();

        var target = container.Resolve<Ticket>();
        Assert.That(target.Issue, Is.EqualTo(new DateTime(2023, 1, 1).AddDays(10)));
    }
}

参考