1.21 jigowatts

Great Scott!

【.NET Core】MoqのQuickstartをやってみる(Verification)

概要

MoqのQuickstartをやってみる。今回はVerifyメソッドです。

github.com

環境
  • macOS Sierra バージョン 10.12.6
  • .NET Core 2.0
  • Moq 4.7.137

テストコード

こんな感じかなぁ。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using MoqQuickstart;

namespace MoqQuickstart.Tests
{
    [TestClass]
    public class Verification
    {
        [TestMethod]
        public void verify()
        {
            var mock = new Mock<IFoo>();
            mock.Object.DoSomething("ping");
            mock.Verify(foo => foo.DoSomething("ping"));
        }

        [TestMethod]
        public void verify_with_custom_error_message_for_failure()
        {
            var mock = new Mock<IFoo>();
            mock.Verify(foo => foo.DoSomething("ping"), "When doing operation X, the service should be pinged always");
        }

        [TestMethod]
        public void method_should_never_be_called()
        {
            var mock = new Mock<IFoo>();
            mock.Verify(foo => foo.DoSomething("ping"), Times.Never());
        }

        [TestMethod]
        public void called_at_least_once()
        {
            var mock = new Mock<IFoo>();
            mock.Object.DoSomething("ping");
            mock.Verify(foo => foo.DoSomething("ping"), Times.AtLeastOnce());
        }

        [TestMethod]
        public void verify_getter_invocation_regardless_of_value()
        {
            var mock = new Mock<IFoo>();
            var name = mock.Object.Name;
            mock.VerifyGet(foo => foo.Name);
        }

        [TestMethod]
        public void verify_setter_invocation_regardless_of_value()
        {
            var mock = new Mock<IFoo>();
            mock.Object.Name = "foo";
            mock.VerifySet(foo => foo.Name);
        }

        [TestMethod]
        public void verify_setter_called_with_specific_value()
        {
            var mock = new Mock<IFoo>();
            mock.Object.Name = "foo";
            mock.VerifySet(foo => foo.Name ="foo");
        }

        [DataTestMethod]
        [DataRow(1)]
        [DataRow(3)]
        [DataRow(5)]
        public void verify_setter_with_an_argument_matcher(int value)
        {
            var mock = new Mock<IFoo>();
            mock.Object.Value = value;
            mock.VerifySet(foo => foo.Value = It.IsInRange(1, 5, Range.Inclusive));
        }
    }
}

テストを実行してみましょう。
verify_with_custom_error_message_for_failureでちゃんとエラーメッセージも表示されていますね!

$ dotnet test --filter "FullyQualifiedName~MoqQuickstart.Tests.Verification"
Build started, please wait...
Build completed.

Test run for /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/bin/Debug/netcoreapp2.0/PrimeService.Tests.dll(.NETCoreApp,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.3.0-preview-20170628-02
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
失敗   MoqQuickstart.Tests.Verification.verify_with_custom_error_message_for_failure
エラー メッセージ:
 Test method MoqQuickstart.Tests.Verification.verify_with_custom_error_message_for_failure threw exception: 
Moq.MockException: When doing operation X, the service should be pinged always
Expected invocation on the mock at least once, but was never performed: foo => foo.DoSomething("ping")
No setups configured.
No invocations performed.
スタック トレース:
    at Moq.Mock.ThrowVerifyException(MethodCall expected, IEnumerable`1 setups, IEnumerable`1 actualCalls, Expression expression, Times times, Int32 callCount)
   at Moq.Mock.VerifyCalls(Interceptor targetInterceptor, MethodCall expected, Expression expression, Times times)
   at Moq.Mock.Verify[T,TResult](Mock`1 mock, Expression`1 expression, Times times, String failMessage)
   at Moq.Mock`1.Verify[TResult](Expression`1 expression, String failMessage)
   at MoqQuickstart.Tests.Verification.verify_with_custom_error_message_for_failure() in /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/Verification.cs:line 23


テストの合計数: 10。成功: 9。失敗:1。スキップ: 0
テストの実行に失敗しました。
テスト実行時間: 2.3554 秒

はい、次!

【.NET Core】MoqのQuickstartをやってみる(Callbacks)

概要

MoqのQuickstartをやってみる。Callbackメソッドについてです。

github.com

環境
  • macOS Sierra バージョン 10.12.6
  • .NET Core 2.0
  • Moq 4.7.137

テストコード

Callbackメソッドの使い方に関しては特に何もないですね。そのまま使えばいいと思うmo(^q^)

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using MoqQuickstart;

namespace MoqQuickstart.Tests
{
    [TestClass]
    public class Callbacks
    {
        [TestMethod]
        public void callback()
        {
            var expected = 2;
            var mock = new Mock<IFoo>();
            var calls = 0;

            mock.Setup(foo => foo.DoSomething("ping"))
                .Returns(true)
                .Callback(() => calls++);

            var f = mock.Object;
            f.DoSomething("ping");
            f.DoSomething("pong");
            f.DoSomething("ping");

            Assert.AreEqual(expected, calls);
        }

        [TestMethod]
        public void access_invocation_arguments()
        {
            var expected = new List<string>(){"foo", "bar", "baz"};
            var mock = new Mock<IFoo>();
            var callArgs = new List<string>();

            mock.Setup(foo => foo.DoSomething(It.IsAny<string>()))
                .Returns(true)
                .Callback((string s) => callArgs.Add(s));

            var f = mock.Object;
            f.DoSomething("foo");
            f.DoSomething("bar");
            f.DoSomething("baz");

            CollectionAssert.AreEqual(expected, callArgs);
        }

        [TestMethod]
        public void calalternate_equivalent_generic_method_syntax()
        {
            var expected = new List<string>(){"foo", "bar", "baz"};
            var mock = new Mock<IFoo>();
            var callArgs = new List<string>();

            mock.Setup(foo => foo.DoSomething(It.IsAny<string>()))
                .Returns(true)
                .Callback<string>(s => callArgs.Add(s));

            var f = mock.Object;
            f.DoSomething("foo");
            f.DoSomething("bar");
            f.DoSomething("baz");

            CollectionAssert.AreEqual(expected, callArgs);
        }
        [TestMethod]
        public void access_arguments_for_methods_with_multiple_parameters()
        {
            var expected = new List<string>(){"foo", "bar", "baz"};

            var mock = new Mock<IFoo>();
            var callArgs = new List<string>();

            mock.Setup(foo => foo.DoSomething(It.IsAny<int>(), It.IsAny<string>()))
                .Returns(true)
                .Callback<int, string>((i, s) => callArgs.Add(s));

            var f = mock.Object;
            f.DoSomething(1, "foo");
            f.DoSomething(2, "bar");
            f.DoSomething(3, "baz");

            CollectionAssert.AreEqual(expected, callArgs);
        }

        [TestMethod]
        public void callbacks_can_be_specified_before_and_after_invocation()
        {
            var mock = new Mock<IFoo>();

            mock.Setup(foo => foo.DoSomething("ping"))
                .Callback(() => Console.WriteLine("Before returns"))
                .Returns(true)
                .Callback(() => Console.WriteLine("After returns"));

            var actual = mock.Object.DoSomething("ping");

            Assert.IsFalse(actual);
        }
    }
}

テストを実行してみましょう。callbacks_can_be_specified_before_and_after_invocationはテスト失敗時に標準出力メッセージが確認できますね。

$ dotnet test --filter "FullyQualifiedName~MoqQuickstart.Tests.Callbacks"
Build started, please wait...
Build completed.

Test run for /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/bin/Debug/netcoreapp2.0/PrimeService.Tests.dll(.NETCoreApp,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.3.0-preview-20170628-02
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
失敗   MoqQuickstart.Tests.Callbacks.callbacks_can_be_specified_before_and_after_invocation
エラー メッセージ:
 Assert.IsFalse failed. 
スタック トレース:
   at MoqQuickstart.Tests.Callbacks.callbacks_can_be_specified_before_and_after_invocation() in /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/Callbacks.cs:line 100

標準出力メッセージ:
 Before returns
 After returns


テストの合計数: 5。成功: 4。失敗:1。スキップ: 0。
テストの実行に失敗しました。
テスト実行時間: 2.4901 秒

【.NET Core】MoqのQuickstartをやってみる(Properties)

概要

MoqのQuickstartをやってみる。次はプロパティの扱いについてです。

github.com

環境
  • macOS Sierra バージョン 10.12.6
  • .NET Core 2.0
  • Moq 4.7.137

テストコード

書かれていない部分はこんな感じなのかなと考えながら書くのでミニヨクツクはず。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using MoqQuickstart;

namespace MoqQuickstart.Tests
{
    [TestClass]
    public class Properties
    {
        [TestMethod]
        public void setup()
        {
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.Name).Returns("bar");

            Assert.AreEqual("bar",mock.Object.Name);
        }

        [TestMethod]
        public void auto_mocking_hierarchies()
        {
            var mock = new Mock<IFoo>();
            mock.Setup(foo => foo.Bar.Baz.Name).Returns("baz");

            Assert.AreEqual("baz",mock.Object.Bar.Baz.Name);
        }

        [TestMethod]
        public void expects_an_invocation_to_set_the_value_to_foo()
        {
            var mock = new Mock<IFoo>(MockBehavior.Strict);
            mock.SetupSet(foo => foo.Name = "foo");

            mock.Object.Name = "foo";
        } 

        [TestMethod]
        public void or_verify_the_setter_directly()
        {
            var mock = new Mock<IFoo>();
            mock.Object.Name = "foo";

            mock.VerifySet(foo => foo.Name = "foo");
        }

        [TestMethod]
        public void setup_a_property_so_that_it_will_automatically_start_tracking_its_value()
        {
            var mock = new Mock<IFoo>();
            // start "tracking" sets/gets to this property
            mock.SetupProperty(f => f.Name);

            // alternatively, provide a default value for the stubbed property
            mock.SetupProperty(f => f.Name, "foo");


            // Now you can do:
            IFoo foo = mock.Object;
            // Initial value was stored
            Assert.AreEqual("foo", foo.Name);

            // New value set which changes the initial value
            foo.Name = "bar";
            Assert.AreEqual("bar", foo.Name);
        }

        [TestMethod]
        public void stub_all_properties_on_a_mock()
        {
            var mock = new Mock<IFoo>();
            mock.SetupAllProperties();

            mock.Object.Name = "foo";
            mock.Object.Value = 123;

            Assert.AreEqual("foo", mock.Object.Name);
            Assert.AreEqual(123, mock.Object.Value);
        }
    }
}

モックに対してプロパティを設定していく方法も何通りかあるんですね。

個人的にSetupSetメソッドがよくわかりませんでした。 プロパティに"foo"が設定されることを期待しているということは、"foo"が設定されなかったらテスト失敗するのかと思っていたのですが、これだと失敗しません。

var mock = new Mock<IFoo>();
mock.SetupSet(foo => foo.Name = "foo");
mock.Object.Name = "bar";

リポジトリ内を検索してみたらMockBehavior.Strictを渡しているサンプルを見つけました。厳密な振る舞いのモック?

var mock = new Mock<IFoo>(MockBehavior.Strict);
mock.SetupSet(foo => foo.Name = "foo");
mock.Object.Name = "bar";

テストを実行してみると失敗します。

$ dotnet test --filter "FullyQualifiedName=MoqQuickstart.Tests.Properties.expects_an_invocation_to_set_the_value_to_foo"
Build started, please wait...
Build completed.

Test run for /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/bin/Debug/netcoreapp2.0/PrimeService.Tests.dll(.NETCoreApp,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.3.0-preview-20170628-02
Copyright (c) Microsoft Corporation.  All rights reserved.

テスト実行を開始しています。お待ちください...
失敗   MoqQuickstart.Tests.Properties.expects_an_invocation_to_set_the_value_to_foo
エラー メッセージ:
 Test method MoqQuickstart.Tests.Properties.expects_an_invocation_to_set_the_value_to_foo threw exception: 
Moq.MockException: IFoo.Name = "bar" invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
スタック トレース:
    at Moq.ExtractProxyCall.HandleIntercept(ICallContext invocation, InterceptorContext ctx, CurrentInterceptContext localctx)
   at Moq.Interceptor.Intercept(ICallContext invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at MoqQuickstart.Tests.Properties.expects_an_invocation_to_set_the_value_to_foo() in /Users/soil/src/github/unit-testing-using-dotnet-test/PrimeService.Tests/Properties.cs:line 35


テストの合計数: 1。成功: 0。失敗:1。スキップ: 0。
テストの実行に失敗しました。
テスト実行時間: 2.4992 秒

他にはCallbackと組み合わせているコードがありました。
MockBehavior.StrictにしてしまうとCallback使うとき不便ですよね。

[TestMethod]
public void setupSet_and_callback()
{
    var mock = new Mock<IFoo>();

    var when = true;
    var positive = false;

    mock.When(() => when).SetupSet(x => x.Name = "foo").Callback(() => positive = true);

    // Strictモードだと"baz"を設定した時点でテスト失敗
    mock.Object.Name = "baz";
    Assert.IsFalse(positive);

    mock.Object.Name = "foo";
    Assert.IsTrue(positive);
}

であればVerifySetを使った方が、プロパティに"foo"が設定されることを期待しているケースでは直感的な感じ。

var mock = new Mock<IFoo>();
mock.Object.Name = "foo";

mock.VerifySet(foo => foo.Name = "foo");

手段が多いことはいいことですmo(^q^)