Friday, 1 December 2017

Mock IAwaitable

When ever you are unit testing chatbots in the botnet framework it's common place to wait for some feedback from the user.

public async Task StartAsync(IDialogContext context)
{
    //display feedback card
    var feedbackCard = base.GetFeedbackMessage(context.MakeMessage(), Contact);
    await context.PostAsync(feedbackCard);

    //wait for feedback
    context.Wait(GetFeedbackAsync);

}

then your GetFeedbackAsync method would look something like

internal async Task GetFeedbackAsync(IDialogContext context, IAwaitable<object> result)
{
    // Stuff to do
    context.Done(null);

}

now the problem is in the IAwaitable<object>, this has to be mocked

private Mock<IAwaitable<IMessageActivity>> GetMoqAwaitableMessageActivity(string textMessage)
{
    var moqMessage = new Mock<IMessageActivity>(MockBehavior.Loose);
    moqMessage.Setup(x => x.Text)
        .Returns(textMessage);

    var moqAwaiter = new Mock<IAwaiter<IMessageActivity>>(MockBehavior.Loose);
    moqAwaiter.Setup(x => x.GetResult())
        .Returns(() => moqMessage.Object);
    moqAwaiter.Setup(x => x.IsCompleted)
        .Returns(true);
           
    var moqAwaitable = new Mock<IAwaitable<IMessageActivity>>();
    moqAwaitable.Setup(x => x.GetAwaiter())
        .Returns(() => moqAwaiter.Object);

    return moqAwaitable;

}

Without getting to in depth into how to implement the async/await syntactical sugar, before mocking IAwaitable we also need to mock IAwaiter and ensure that the IsCompleted property returns true.

now we can test our GetFeedbackAsync method

[TestMethod]
public async Task Dialog_Feedback_Text_Jibberish()
{
    //arange
    var moqDialogContext = GetMoqDialogContext();
    var moqAwaitableMessage = GetMoqAwaitableMessageActivity("bla bla bla");
    var feedbackDialog = new FeedbackDialog();

   //act
    await feedbackDialog.GetFeedbackAsync(moqDialogContext.Object, moqAwaitableMessage.Object);

   //assert
    moqDialogContext.Verify(x => x.PostAsync(It.IsAny<IMessageActivity>(), default(CancellationToken)), Times.Exactly(2));
    moqDialogContext.Verify(x => x.Wait(It.IsAny<ResumeAfter<IMessageActivity>>()), Times.Once);
}


Obviously your assert section will vary, but by mocking the context and the iawaitable we can achieve some degree of unit testing. 

Oh and if you don't know i'm using the Moq nuget package.