Bot Builder v4.5 のユニットテスト : コントローラーのテスト

今回でこのシリーズは最終回です。
[前回まで](https://qiita.com/kenakamu/items/82b3f26e10457481016b)ダイアログのテストを見ていきましたが、今回はコントローラーのテストを見ていきます。

# コントローラーのテスト

Bot Builder v4 でのユニットテストを連載した際は、コントローラーはボットの実行に直接関係が薄く、また変更することもあまりないことから、あまりテストをしませんでした。今回はテンプレートに含まれているため、しっかりとテストしておきましょう。

# PostAsyncCallsProcessAsyncOnAdapter テスト

BotBuilder で開発するボットは実質 Web API であり、POST メソッドを待ち受ける 1 つのコントローラーです。よってテストも Post メソッドのテスト 1 つのみです。

```csharp
[Fact]
public async Task PostAsyncCallsProcessAsyncOnAdapter()
{
    // Create MVC infrastructure mocks and objects
    var request = new Mock<HttpRequest>();
    var response = new Mock<HttpResponse>();
    var mockHttpContext = new Mock<HttpContext>();
    mockHttpContext.Setup(x => x.Request).Returns(request.Object);
    mockHttpContext.Setup(x => x.Response).Returns(response.Object);
    var actionContext = new ActionContext(mockHttpContext.Object, new RouteData(), new ControllerActionDescriptor());

    // Create BF mocks
    var mockAdapter = new Mock<IBotFrameworkHttpAdapter>();
    mockAdapter
        .Setup(x => x.ProcessAsync(It.IsAny<HttpRequest>(), It.IsAny<HttpResponse>(), It.IsAny<IBot>(), It.IsAny<CancellationToken>()))
        .Returns(Task.CompletedTask);
    var mockBot = new Mock<IBot>();

    // Create and initialize controller
    var sut = new BotController(mockAdapter.Object, mockBot.Object)
    {
        ControllerContext = new ControllerContext(actionContext),
    };

    // Invoke the controller
    await sut.PostAsync();

    // Assert
    mockAdapter.Verify(
        x => x.ProcessAsync(
            It.Is<HttpRequest>(o => o == request.Object),
            It.Is<HttpResponse>(o => o == response.Object),
            It.Is<IBot>(o => o == mockBot.Object),
            It.IsAny<CancellationToken>()),
        Times.Once);
}
```

## 各種サービスのモック

依存するサービスを Moq フレームワークでモックします。

### HTTP 要求と応答および HttpConetxt

実際に誰から要求を送ってくるわけではないため、要求と応答をモックします。

```csharp
var request = new Mock<HttpRequest>();
var response = new Mock<HttpResponse>();
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(x => x.Request).Returns(request.Object);
mockHttpContext.Setup(x => x.Response).Returns(response.Object);
```

### ActionContext のモック

テンプレートは ASP.NET Core 2.1 ベースのため、 Microsoft.AspNetCore.Mvc 名前空間の ActionContext もモックしています。後程 ControllerContext に渡されます。

```csharp
var actionContext = new ActionContext(mockHttpContext.Object, new RouteData(), new ControllerActionDescriptor());
```

### アダプターのモック

コントローラーから呼び出される BotBuilder のコンポーネントはアダプターです。また ProcessAsync メソッドが実行されます。詳細は [Bot Builder v4 でボット開発 : アダプター、TurnContext、Activity](https://qiita.com/kenakamu/items/55c93c044a9ee99bbe22) を参照してください。

```csharp
var mockAdapter = new Mock<IBotFrameworkHttpAdapter>();
mockAdapter
    .Setup(x => x.ProcessAsync(It.IsAny<HttpRequest>(), It.IsAny<HttpResponse>(), It.IsAny<IBot>(), It.IsAny<CancellationToken>()))
    .Returns(Task.CompletedTask);
```

### IBot のモック

特定のボットを起動するテストではないため、 IBot もモックします。

```csharp
var mockBot = new Mock<IBot>();
```
## テストの実行

テストはシンプルにコントローラーに対して Post を実行します。

```csharp
// Create and initialize controller
var sut = new BotController(mockAdapter.Object, mockBot.Object)
{
    ControllerContext = new ControllerContext(actionContext),
};

// Invoke the controller
await sut.PostAsync();
```

## 結果の検証

結果はアダプタで一度だけ ProcessAsync が呼ばれたかを確認します。

```csharp
// Assert
mockAdapter.Verify(
    x => x.ProcessAsync(
        It.Is<HttpRequest>(o => o == request.Object),
        It.Is<HttpResponse>(o => o == response.Object),
        It.Is<IBot>(o => o == mockBot.Object),
        It.IsAny<CancellationToken>()),
    Times.Once);
```

# まとめ

このシリーズでは BotBuilder SDK v4.5 で提供されたユニットテスト機能を見てきました。TestFlow や TestAdapter、IRecognizer などの利用についてはこれまで通りですが、テンプレートとしてはじめからユニットテスト付きのソリューションを作れることが最大のポイントだと考えています。是非ユニットテストを充実させて、DevOps を実現してください。

[目次へ戻る](https://qiita.com/kenakamu/items/6dc043cfc1f199032883)