Create Bot for Microsoft Graph with DevOps 1: Unit Testing Bot Framework application
In this series, I will develop a chat bot by using Microsoft Bot Framework and Graph API with VSTS to enable DevOps.
Unit Tests for Bot Framework
Testing is one of the key concept to make DevOps successful, but unit testing of Bot Framework is different from other projects such as MVC. The article below explain the concept very well, so I won’t duplicate the effort to explain it here.
Unit Testing for Bot Applications
https://www.microsoft.com/reallifecode/2017/01/20/unit-testing-for-bot-applications/
Thanks to Catalyst team and Mor Shemesh who shared their precious knowledge and allow me to re-use your contents.
Development Environment
VSTS project
VSTS (Visual Studio Team Services) is Microsoft tool which I use for this project. If you don’t have VSTS account, signup the trial.
1. Create new project in VSTS.
2. I specified Git as version control and Agile for template.
3. Once project setup, click [Clone in Visual Studio]
4. Map to local folder.
1. Create new solution in Team Explorer
2. Select Bot Application template. I name the project as O365 Bot.
3. Add Unit Test Project, too. I name it as O365Bot.UnitTests.
Create unit test helper
BotBuilder github has great resource for unit testing.
1. Download source code from BotBuilder GitHub (https://github.com/Microsoft/BotBuilder)
2. Add a Helper folder in the Unit Test project.
3. Copy below files into the helper folder.
– BotBuilder-master\CSharp\Tests\Microsoft.Bot.Builder.Tests\ConversationTest.cs
– BotBuilder-master\CSharp\Tests\Microsoft.Bot.Builder.Tests\DialogTestBase.cs
– BotBuilder-master\CSharp\Tests\Microsoft.Bot.Builder.Tests\FiberTests.cs
As these files also include test class, delete followings.
– ConversationTest class in ConversationTest.cs
– FiberTests class in FiberTest.cs
4. Add following NuGet packages and apply latest versions for both bot and unit test projects.
– BotBuilder
– Moq
* I didn’t update MSTest packages as the latest version didn’t work well. The version I use is 1.1.11
5. Add System.Web reference and O365Bot project reference.
6. Build the solution once.
Add unit test and execute
All the prerequisites are ready. Let’s add test.
1. Replace the code in UnitTest1.cs with following.
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Tests;
using Microsoft.Bot.Connector;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;
using Autofac;
using O365Bot.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Builder.Base;
using System.Threading;
using System.Collections.Generic;
namespace O365Bot.UnitTests
{
[TestClass]
public class SampleDialogTest : DialogTestBase
{
[TestMethod]
public async Task ShouldReturnCount()
{
// Instantiate dialog to test
IDialog<object> rootDialog = new RootDialog();
// Create in-memory bot environment
Func<IDialog<object>> MakeRoot = () => rootDialog;
using (new FiberTestBase.ResolveMoqAssembly(rootDialog))
using (var container = Build(Options.MockConnectorFactory | Options.ScopedQueue, rootDialog))
{
// Create a message to send to bot
var toBot = DialogTestBase.MakeTestMessage();
toBot.From.Id = Guid.NewGuid().ToString();
toBot.Text = "hi!";
// Send message and check the answer.
IMessageActivity toUser = await GetResponse(container, MakeRoot, toBot);
// Verify the result
Assert.IsTrue(toUser.Text.Equals("You sent hi! which was 3 characters"));
}
}
/// <summary>
/// Send a message to the bot and get repsponse.
/// </summary>
public async Task<IMessageActivity> GetResponse(IContainer container, Func<IDialog<object>> makeRoot, IMessageActivity toBot)
{
using (var scope = DialogModule.BeginLifetimeScope(container, toBot))
{
DialogModule_MakeRoot.Register(scope, makeRoot);
// act: sending the message
using (new LocalizedScope(toBot.Locale))
{
var task = scope.Resolve<IPostToBot>();
await task.PostAsync(toBot, CancellationToken.None);
}
//await Conversation.SendAsync(toBot, makeRoot, CancellationToken.None);
return scope.Resolve<Queue<IMessageActivity>>().Dequeue();
}
}
}
}
2. Build the solution.
3. Run the test from Text Explore.
Summery
I will add AuthBot to enable ADAL authentication for Office 365 in the next article. Don’t forget to Check-in the code to VSTS.
GitHub: https://github.com/kenakamu/BotWithDevOps-Blog-sample/tree/master/article1
Ken
Comments
- Anonymous
September 02, 2017
The method "GetResponse" returns the first response on in the queue. If I have a method that returns multiple messages, using dialogContext.PostAsync(...), how can i get all of the messages for a certain event. As it stands right now, I have to unit test based on the first message returned.Nice tutorial, has helped A LOT!- Anonymous
September 02, 2017
Check out https://blogs.msdn.microsoft.com/kenakamu/2017/06/29/devops-with-bot-framework-chat-for-business-application-8-en/ which has the method for you- Anonymous
September 03, 2017
Thank you sir! are you on twitter?- Anonymous
September 03, 2017
No I don't as I don't prefer tweeting to blogging.
- Anonymous
- Anonymous
- Anonymous
- Anonymous
September 22, 2017
How would I pre set state data, to test different conditions within a dialog. Currently, I need a dialog context to save/load user data.- Anonymous
September 25, 2017
I usually send message to bot to create context. What exactly you need to do?- Anonymous
September 25, 2017
I have a root dialog, that checks a flag in user state. If the flag is set, it calla into dialog 1, otherwise calla dialog 2.That is just one example.Another example, is a dialog that stores a value, would like to be able to validate the item was stored in user state upon dialog done.Let me know if you need more examples- Anonymous
September 26, 2017
What I do is follows.Of course you may want to move the place where I set data, but it should give you some idea? public async Task GetResponse(IContainer container, Func<IDialog> makeRoot, IMessageActivity toBot) { using (var scope = DialogModule.BeginLifetimeScope(container, toBot)) { DialogModule_MakeRoot.Register(scope, makeRoot); // Set botData here. var botData = scope.Resolve(); await botData.LoadAsync(CancellationToken.None); botData.UserData.SetValue("My", "Mydata"); // act: sending the message using (new LocalizedScope(toBot.Locale)) { var task = scope.Resolve(); await task.PostAsync(toBot, CancellationToken.None); } //await Conversation.SendAsync(toBot, makeRoot, CancellationToken.None); return scope.Resolve<Queue>().Dequeue(); } }- Anonymous
September 27, 2017
I had to make one slight change"var botData = scope.Resolve();" --> "var botData = scope.Resolve();" and it works as expected.Thanks much!- Anonymous
September 29, 2017
It's great to hear that it worked!
- Anonymous
- Anonymous
- Anonymous
- Anonymous
- Anonymous
- Anonymous
March 22, 2018
This example works well with Microsoft. Bot. Builder version 3.8, but don't work with 3.13.0.3 o later versionHow do I get it to work with the latest version of SDK bot?Thanks- Anonymous
March 22, 2018
Thanks Claudio, I personally didn't try the latest version, but I guess the internal structure has been changed if this doesn't work anymore. I will look into it when I have time but please try to find alternative on other places as well :)
- Anonymous