今回でこのシリーズは最終回です。
前回までダイアログのテストを見ていきましたが、今回はコントローラーのテストを見ていきます。
コントローラーのテスト
Bot Builder v4 でのユニットテストを連載した際は、コントローラーはボットの実行に直接関係が薄く、また変更することもあまりないことから、あまりテストをしませんでした。今回はテンプレートに含まれているため、しっかりとテストしておきましょう。
PostAsyncCallsProcessAsyncOnAdapter テスト
BotBuilder で開発するボットは実質 Web API であり、POST メソッドを待ち受ける 1 つのコントローラーです。よってテストも Post メソッドのテスト 1 つのみです。
[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
実際に誰から要求を送ってくるわけではないため、要求と応答をモックします。
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 に渡されます。
var actionContext = new ActionContext(mockHttpContext.Object, new RouteData(), new ControllerActionDescriptor());
アダプターのモック
コントローラーから呼び出される BotBuilder のコンポーネントはアダプターです。また ProcessAsync メソッドが実行されます。詳細は Bot Builder v4 でボット開発 : アダプター、TurnContext、Activity を参照してください。
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 もモックします。
var mockBot = new Mock<IBot>();
テストの実行
テストはシンプルにコントローラーに対して Post を実行します。
// Create and initialize controller
var sut = new BotController(mockAdapter.Object, mockBot.Object)
{
ControllerContext = new ControllerContext(actionContext),
};
// Invoke the controller
await sut.PostAsync();
結果の検証
結果はアダプタで一度だけ ProcessAsync が呼ばれたかを確認します。
// 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 を実現してください。