Create Bot for Microsoft Graph with DevOps 3: Unit Testing with Moq, AutoFac and Fakes
In this article, I create an unit test for the feature I implemented in the last article.
Mocking the service
To do unit testing, you need to mock all dependencies. In this case, Graph Service and AuthBot are the ones.
Mocking AuthBot
By looking into the source code of AuthBot, it add extend method to IDialogContext. To mock this kind of dependency, you can use Microsoft Fakes, which comes with Visual Studio.
1. To create “fake” assembly, add AuthBot NuGet package to the unit test project. Apply all updates except TestPlatform packages. *I kept them as v.1.1.11.
2. Right click AuthBotin the References, then select [Add Fakes Assembly].
3. Remove ShouldReturnCount method and add following method in UnitTest1.cs, then compile the solution. At this moment, I only mock AuthBot so test shall fail.
[TestMethod]
public async Task ShouldReturnEvents()
{
// Instantiate ShimsContext to use Fakes
using (ShimsContext.Create())
{
// Return "dummyToken" when calling GetAccessToken method
AuthBot.Fakes.ShimContextExtensions.GetAccessTokenIBotContextString =
async (a, e) => { return "dummyToken"; };
// 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"));
}
}
}
4. Put break point at line 27 and start debugging the test.
5. Once the breakpoint hit, then press F10 to step over. You can see it passes the authentication check as the GetAccessToken method returns “dummy token”.
6. As I mentioned, the test shall fail as I didn’t mock Microsoft Graph service yet.
Mocking Microsoft Graph Service
I use Moq and AutoFac to mock this because I own the source code.
1. Open GraphService.cs, then create Interface from the class.
2. I name the interface as IEventService. Click OK to create it.
3. Then setup the AutoFac. Replace the code in Global.asax.cs. With this code, AutoFac “resolves” IEventService as GraphService when requested.
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. Open RootDialog.cs and add using statement.
using Autofac;
5. Replace the MessageReceivedAsync method with following code. This uses AutoFac to resolve the service to concrete class instance.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var message = await result as Activity;
// Check authentication
if (string.IsNullOrEmpty(await context.GetAccessToken(ConfigurationManager.AppSettings["ActiveDirectory.ResourceId"])))
{
// Run authentication dialog
await context.Forward(new AzureAuthDialog(ConfigurationManager.AppSettings["ActiveDirectory.ResourceId"]), this.ResumeAfterAuth, message, CancellationToken.None);
}
else
{
using (var scope = WebApiApplication.Container.BeginLifetimeScope())
{
// Resolve IEventService to concrete type. Pass IDialogContext object to constructor
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}");
}
}
}
}
6. In unit testing project, add Microsoft.Graph NuGet package.
7. Open UnitTest1.cs and add using statements.
using Moq;
using Microsoft.Graph;
using O365Bot.Services;
8. Add following code before instantiate dialog to mock the Graph service. This will return a dummy event every time.
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"
}
}
});
var builder = new ContainerBuilder();
builder.RegisterInstance(mockEventService.Object).As<IEventService>();
WebApiApplication.Container = builder.Build();
9. Replace asserting with following code.
Assert.IsTrue(toUser.Text.Equals("2017-05-31 12:00-2017-05-31 13:00: dummy event"));
10. Compile the solution and the run the test.
Summery
There are many ways to mock the dependencies and it is actually fun to do it. I will setup Continuous Integration next.
GitHub: https://github.com/kenakamu/BotWithDevOps-Blog-sample/tree/master/article3
Ken