Bot Framework と Microsoft Graph で DevOps その 4 : Microsoft Fakes, AutoFac, Moq で依存サービスのモック化

前回は AuthBot と Microsoft Graph を呼べるようにしました。今回は変更したものをユニットテストしてみます。

依存サービスのモック

依存サービスはモックを作り、常に意図した動作をするようにすることで、本当にテストしたい部分のユニットテストを行います。AuthBot と GraphService それぞれモック化します。

AuthBot のモック化

コード内で AuthBot の GetAccessToken メソッドを実行して、OAuth 2.0 のアクセストークンを取得していますが、ここをモック化します。ソースコードを見ると GetAccessToken は静的な部分メソッドですので、Microsoft Fakes を使います。Fakes の詳細はこちら

1. Fake アセンブリを作るため、ユニットテストプロジェクトにも AuthBot を追加して、各種 NuGet パッケージを最新に更新します。

2. AuthBot 参照を右クリックして、Fakes Assembly を追加します。

image

3. UnitTest1.cs の ShouldReturnCount メソッドを削除して、以下のように書き換えます。

[TestMethod]
public async Task ShouldReturnEvents()
{
    // Fakes を使うためにコンテキストを作成 
    using (ShimsContext.Create())
    {
        // AuthBot の GetAccessToken メソッドを実行した際、dummyToken というトークンが返るよう設定 
        AuthBot.Fakes.ShimContextExtensions.GetAccessTokenIBotContextString = 
            async (a, e) => { return "dummyToken"; };

        // テストしたいダイアログのインスタンス作成 
        IDialog rootDialog = new RootDialog();

        // Bot に送るメッセージを作成 
        var toBot = DialogTestBase.MakeTestMessage();
        toBot.From.Id = Guid.NewGuid().ToString();
        toBot.Text = "get appointments";

        // メモリ内で実行できる環境を作成 
        Func<IDialog> MakeRoot = () => rootDialog;
        using (new FiberTestBase.ResolveMoqAssembly(rootDialog))
        using (var container = Build(Options.MockConnectorFactory | Options.ScopedQueue, rootDialog))
        {
            // メッセージを送信して、結果を受信 
            IMessageActivity toUser = await GetResponse(container, MakeRoot, toBot);
            // 結果の検証 
            Assert.IsTrue(toUser.Text.Equals("You sent hi! which was 3 characters"));
        }
    }
}

4. RootDialog.cs の 認証チェックのコードあたりにブレークポイントを置きます。

image

5. 一旦ソリューションをコンパイルして、テストエクスプローラーより ShouldReturnEvents テストをデバッグ実行します。

image

6. ブレークポイントがヒットしたらステップ実行して、認証済ロジックに遷移することを確認します。

image

これで AuthBot の依存は解決しました。

GraphService のモック化

AuthBot と違いソースをもっているので、サービスをインターフェース化して、Moq と AutoFac を使います。
Moq の詳細はこちら。
AutoFac の詳細はこちら。

1. GraphService.cs を開き、GraphService からインターフェースを作成します。

image

2. インターフェース名は IEventService としました。

image

3. 次に AutoFac のセットアップをします。Global.asax.cs の中身を以下に差し替えます。IEventService を利用する際、GraphService を返します。

using Autofac;
using O365Bot.Services;
using System.Configuration;
using System.Web.Http;

namespace O365Bot
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        public static IContainer Container;

        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);

            AuthBot.Models.AuthSettings.Mode = ConfigurationManager.AppSettings["ActiveDirectory.Mode"];
            AuthBot.Models.AuthSettings.EndpointUrl = ConfigurationManager.AppSettings["ActiveDirectory.EndpointUrl"];
            AuthBot.Models.AuthSettings.Tenant = ConfigurationManager.AppSettings["ActiveDirectory.Tenant"];
            AuthBot.Models.AuthSettings.RedirectUrl = ConfigurationManager.AppSettings["ActiveDirectory.RedirectUrl"];
            AuthBot.Models.AuthSettings.ClientId = ConfigurationManager.AppSettings["ActiveDirectory.ClientId"];
            AuthBot.Models.AuthSettings.ClientSecret = ConfigurationManager.AppSettings["ActiveDirectory.ClientSecret"];

            var builder = new ContainerBuilder();
            builder.RegisterType<GraphService>().As<IEventService>();
            Container = builder.Build();
        }
    }
}

4. RootDialog.cs ファイルの認証済ロジック内を以下のコードをに差し替えます。これにより実行時に動的にサービスをの実装を取得できます。

using (var scope = WebApiApplication.Container.BeginLifetimeScope())
{
    // 認証済の場合のロジック実行
    // IEventService の実体を取得する
    // コンストラクタに IDialog context を渡す
    IEventService service = scope.Resolve<IEventService>(new TypedParameter(typeof(IDialogContext), context));
    var events = await service.GetEvents();
    foreach (var @event in events)
    {
        await context.PostAsync($"{@event.Start.DateTime}-{@event.End.DateTime}: {@event.Subject}");
    }
}

5. ユニットテストプロジェクトに戻って、Microsoft.Graph NuGet パッケージを追加します。

6. UnitTest1.cs を開き、以下 using を追加します。

using Moq;
 using Microsoft.Graph;

7. ShouldReturnEvents テストメソッド、IDialog rootDialog = new RootDialog(); の前に以下のコードを追加します。これでテスト時にはモックのサービスを利用します。

// サービスのモック
var mockEventService = new Mock<IEventService>();
mockEventService.Setup(x => x.GetEvents()).ReturnsAsync(new List<Event>()
{
    new Event
    {
        Subject = "dummy event",
        Start = new DateTimeTimeZone()
        {
            DateTime = "2017-05-31 12:00",
            TimeZone = "Standard Tokyo Time"
        },
        End = new DateTimeTimeZone()
        {
            DateTime = "2017-05-31 13:00",
            TimeZone = "Standard Tokyo Time"
        }
    }
});
// IEventService 解決時にモックが返るよう設定
var builder = new ContainerBuilder();
builder.RegisterInstance(mockEventService.Object).As<IEventService>();
WebApiApplication.Container = builder.Build();

8. 先ほどと同じようにテストをデバッグ実行。指定したダミーのイベントが取得されていることを確認。

image

テストの改修

全てモック化できたので、最後にテストの検証個所を書き換えてテストしましょう。

1. 結果検証を以下のコードに差し替え。

// 結果の検証
Assert.IsTrue(toUser.Text.Equals("2017-05-31 12:00-2017-05-31 13:00: dummy event"));

2. テストエクスプローラーよりテストの実行。

image

3. テストが成功することを確認。

image

まとめ

今回は依存サービスのモック化を実施しました。次回は VSTS 側での CI を設定します。コードはすべてチェックインしておいてください。