First pass a TestBot unit tests

This commit is contained in:
Gabo Gilabert 2019-06-14 14:46:18 -04:00
Родитель a9705e1104
Коммит f0f243277e
30 изменённых файлов: 1295 добавлений и 193 удалений

Просмотреть файл

@ -82,6 +82,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Testi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Testing.Tests", "tests\Microsoft.Bot.Builder.Testing.Tests\Microsoft.Bot.Builder.Testing.Tests.csproj", "{E4E13301-9193-4106-B0E3-41276B478E7C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.TestBot.Tests", "tests\Microsoft.Bot.Builder.TestBot.Tests\Microsoft.Bot.Builder.TestBot.Tests.csproj", "{76391566-9F22-4994-8B0F-02EFC0E9E228}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU
@ -361,6 +363,14 @@ Global
{E4E13301-9193-4106-B0E3-41276B478E7C}.Documentation|Any CPU.Build.0 = Debug|Any CPU
{E4E13301-9193-4106-B0E3-41276B478E7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4E13301-9193-4106-B0E3-41276B478E7C}.Release|Any CPU.Build.0 = Release|Any CPU
{76391566-9F22-4994-8B0F-02EFC0E9E228}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{76391566-9F22-4994-8B0F-02EFC0E9E228}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{76391566-9F22-4994-8B0F-02EFC0E9E228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{76391566-9F22-4994-8B0F-02EFC0E9E228}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76391566-9F22-4994-8B0F-02EFC0E9E228}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
{76391566-9F22-4994-8B0F-02EFC0E9E228}.Documentation|Any CPU.Build.0 = Debug|Any CPU
{76391566-9F22-4994-8B0F-02EFC0E9E228}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76391566-9F22-4994-8B0F-02EFC0E9E228}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -403,6 +413,7 @@ Global
{B9DDC8CB-8EDF-4D98-913A-22F19E642223} = {8667F820-8ADA-4498-91AE-AE95DEE5227E}
{060F070A-BBFA-490E-BE89-3844C857B771} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{E4E13301-9193-4106-B0E3-41276B478E7C} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{76391566-9F22-4994-8B0F-02EFC0E9E228} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7173C9F3-A7F9-496E-9078-9156E35D6E16}

Просмотреть файл

@ -100,7 +100,7 @@ namespace Microsoft.Bot.Builder.Testing
}
/// <summary>
/// Gets and returns the next bot response./>.
/// Gets the next bot response./>.
/// </summary>
/// <returns>The next activity in the queue; or null, if the queue is empty.</returns>
/// <typeparam name="T">An <see cref="IActivity"/> derived type.</typeparam>

Просмотреть файл

@ -15,6 +15,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<DocumentationFile />
</PropertyGroup>
<PropertyGroup>
@ -26,7 +27,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>C:\Projects\Repos\botbuilder-dotnet\libraries\Microsoft.Bot.Builder.Testing\Microsoft.Bot.Builder.Testing.xml</DocumentationFile>
<DocumentationFile></DocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
</PropertyGroup>
@ -34,11 +35,13 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Documentation|AnyCPU'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<DocumentationFile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug - NuGet Packages|AnyCPU'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<DocumentationFile />
</PropertyGroup>
<ItemGroup>

Просмотреть файл

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Rules for StyleCop.Analyzers" Description="Code analysis rules for StyleCop.Analyzers.csproj." ToolsVersion="14.0">
<Rules AnalyzerId="AsyncUsageAnalyzers" RuleNamespace="AsyncUsageAnalyzers">
<Rule Id="UseConfigureAwait" Action="Warning" />
<Rule Id="AvoidAsyncVoid" Action="Warning" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.Features" RuleNamespace="Microsoft.CodeAnalysis.CSharp.Features">
<Rule Id="IDE0003" Action="None" />
</Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<Rule Id="SA1101" Action="None" /> <!-- local calls prefixed with this -->
<Rule Id="SA1129" Action="None" /> <!-- don't use value type constructor-->
<Rule Id="SA1200" Action="None" />
<Rule Id="SA1214" Action ="None"/> <!-- encapsalate field in property-->
<Rule Id="SA1305" Action="Warning" />
<Rule Id="SA1309" Action="None" />
<Rule Id="SA1412" Action="Warning" />
<Rule Id="SA1600" Action="None" />
<Rule Id="SA1609" Action="Warning" />
<Rule Id="SA1633" Action="None" />
</Rules>
</RuleSet>

Просмотреть файл

@ -1,159 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>Microsoft.Bot.Builder.Testing</name>
</assembly>
<members>
<member name="T:Microsoft.Bot.Builder.Testing.DialogTestClient">
<summary>
A client to for testing dialogs in isolation.
</summary>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.DialogTestClient.#ctor(System.String,Microsoft.Bot.Builder.Dialogs.Dialog,System.Object,System.Collections.Generic.IEnumerable{Microsoft.Bot.Builder.IMiddleware},Microsoft.Bot.Builder.BotCallbackHandler)">
<summary>
Initializes a new instance of the <see cref="T:Microsoft.Bot.Builder.Testing.DialogTestClient"/> class.
</summary>
<param name="channelId">
The channelId (see <see cref="T:Microsoft.Bot.Connector.Channels"/>) to be used for the test.
Use <see cref="F:Microsoft.Bot.Connector.Channels.Emulator"/> or <see cref="F:Microsoft.Bot.Connector.Channels.Test"/> if you are uncertain of the channel you are targeting.
Otherwise, it is recommended that you use the id for the channel(s) your bot will be using.
Consider writing a test case for each channel.
</param>
<param name="targetDialog">The dialog to be tested. This will be the root dialog for the test client.</param>
<param name="initialDialogOptions">(Optional) additional argument(s) to pass to the dialog being started.</param>
<param name="middlewares">(Optional) A list of middlewares to be added to the test adapter.</param>
<param name="callback">(Optional) The bot turn processing logic for the test. If this value is not provided, the test client will create a default <see cref="T:Microsoft.Bot.Builder.BotCallbackHandler"/>.</param>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.DialogTestClient.#ctor(Microsoft.Bot.Builder.Adapters.TestAdapter,Microsoft.Bot.Builder.Dialogs.Dialog,System.Object,System.Collections.Generic.IEnumerable{Microsoft.Bot.Builder.IMiddleware},Microsoft.Bot.Builder.BotCallbackHandler)">
<summary>
Initializes a new instance of the <see cref="T:Microsoft.Bot.Builder.Testing.DialogTestClient"/> class.
</summary>
<param name="testAdapter">The <see cref="T:Microsoft.Bot.Builder.Adapters.TestAdapter"/> to use.</param>
<param name="targetDialog">The dialog to be tested. This will be the root dialog for the test client.</param>
<param name="initialDialogOptions">(Optional) additional argument(s) to pass to the dialog being started.</param>
<param name="middlewares">(Optional) A list of middlewares to be added to the test adapter.</param>
<param name="callback">(Optional) The bot turn processing logic for the test. If this value is not provided, the test client will create a default <see cref="T:Microsoft.Bot.Builder.BotCallbackHandler"/>.</param>
</member>
<member name="P:Microsoft.Bot.Builder.Testing.DialogTestClient.DialogTurnResult">
<summary>
Gets the latest <see cref="P:Microsoft.Bot.Builder.Testing.DialogTestClient.DialogTurnResult"/> for the dialog being tested.
</summary>
<value>A <see cref="P:Microsoft.Bot.Builder.Testing.DialogTestClient.DialogTurnResult"/> instance with the result of the last turn.</value>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.DialogTestClient.SendActivityAsync``1(Microsoft.Bot.Schema.Activity,System.Threading.CancellationToken)">
<summary>
Sends an <see cref="T:Microsoft.Bot.Schema.Activity"/> to the target dialog.
</summary>
<param name="activity">The activity to send.</param>
<param name="cancellationToken">The cancellation token.</param>
<returns>A <see cref="T:System.Threading.Tasks.Task"/> representing the result of the asynchronous operation.</returns>
<typeparam name="T">An <see cref="T:Microsoft.Bot.Schema.IActivity"/> derived type.</typeparam>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.DialogTestClient.SendActivityAsync``1(System.String,System.Threading.CancellationToken)">
<summary>
Sends a message activity to to the target dialog.
</summary>
<param name="text">The text of the message to send.</param>
<param name="cancellationToken">The cancellation token.</param>
<returns>A <see cref="T:System.Threading.Tasks.Task"/> representing the result of the asynchronous operation.</returns>
<typeparam name="T">An <see cref="T:Microsoft.Bot.Schema.IActivity"/> derived type.</typeparam>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.DialogTestClient.GetNextReply``1">
<summary>
Gets and returns the next bot response./>.
</summary>
<returns>The next activity in the queue; or null, if the queue is empty.</returns>
<typeparam name="T">An <see cref="T:Microsoft.Bot.Schema.IActivity"/> derived type.</typeparam>
</member>
<member name="T:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject">
<summary>
A wrapper class for XUnit test data that enables support for enumerating test cases in Test Explorer.
</summary>
<remarks>
VS Test explorer only supports value types for data driven tests.
This class implements <see cref="T:Xunit.Abstractions.IXunitSerializable"/> and serializes complex types as json
so the test cases can be enumerated and displayed into VS test explorer.
This also allows the developer to right click on a particular test case on VS Test explorer and run it individually.
</remarks>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.#ctor(System.Object)">
<summary>
Initializes a new instance of the <see cref="T:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject"/> class.
</summary>
<param name="testData">An object with the data to be used in the test.</param>
</member>
<member name="P:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.TestObject">
<summary>
Gets a json string with the test data object.
</summary>
<value>The test data object as a json string.</value>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.Deserialize(Xunit.Abstractions.IXunitSerializationInfo)">
<summary>
Used by XUnit.net for deserialization.
</summary>
<param name="serializationInfo">A parameter used by XUnit.net.</param>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.Serialize(Xunit.Abstractions.IXunitSerializationInfo)">
<summary>
Used by XUnit.net for serialization.
</summary>
<param name="serializationInfo">A parameter used by XUnit.net.</param>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.GetObject``1">
<summary>
Gets the test data object for the specified .Net type.
</summary>
<typeparam name="T">The type of the object to be returned.</typeparam>
<returns>The test object instance.</returns>
</member>
<member name="T:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware">
<inheritdoc />
<summary>
A middleware to output incoming and outgoing activities as json strings to the console during
unit tests.
</summary>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware.#ctor(Xunit.Abstractions.ITestOutputHelper)">
<summary>
Initializes a new instance of the <see cref="T:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware"/> class.
</summary>
<remarks>
This middleware outputs the incoming and outgoing activities for the XUnit based test to the console window.
If you need to output the incoming and outgoing activities to some other provider consider using
the <see cref="T:Microsoft.Bot.Builder.TranscriptLoggerMiddleware"/> instead.
</remarks>
<param name="xunitOutputHelper">
An XUnit <see cref="T:Xunit.Abstractions.ITestOutputHelper"/> instance.
See <see href="https://xunit.net/docs/capturing-output.html">Capturing Output</see> in the XUnit documentation for additional details.
</param>
</member>
<member name="P:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware.Output">
<summary>
Gets the <see cref="T:Xunit.Abstractions.ITestOutputHelper"/> instance for this middleware.
</summary>
<value>The <see cref="T:Xunit.Abstractions.ITestOutputHelper"/> instance for this middleware.</value>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware.LogIncomingActivityAsync(Microsoft.Bot.Builder.ITurnContext,Microsoft.Bot.Schema.Activity,System.Threading.CancellationToken)">
<summary>
Logs messages sent from the user to the bot.
</summary>
<remarks>
<see cref="F:Microsoft.Bot.Schema.ActivityTypes.Message"/> activities will be logged as text. Other activities will be logged as json.
</remarks>
<param name="context">The context object for this turn.</param>
<param name="activity">The <see cref="T:Microsoft.Bot.Schema.Activity"/> to be logged.</param>
<param name="cancellationToken">The cancellation token.</param>
<returns>A task that represents the work to execute.</returns>
</member>
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware.LogOutgoingActivityAsync(Microsoft.Bot.Builder.ITurnContext,Microsoft.Bot.Schema.Activity,System.Threading.CancellationToken)">
<summary>
Logs messages sent from the bot to the user.
</summary>
<param name="context">The context object for this turn.</param>
<param name="activity">The <see cref="T:Microsoft.Bot.Schema.Activity"/> to be logged.</param>
<param name="cancellationToken">The cancellation token.</param>
<returns>A task that represents the work to execute.</returns>
</member>
</members>
</doc>

Просмотреть файл

@ -21,6 +21,10 @@
<Description>Library for building bots using Microsoft Bot Framework Connector</Description>
<Summary>Library for building bots using Microsoft Bot Framework Connector</Summary>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Documentation|AnyCPU'">
<DocumentationFile></DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AsyncUsageAnalyzers" Version="1.0.0-alpha003" PrivateAssets="all" />

Просмотреть файл

@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Microsoft.BotBuilderSamples.Tests.Framework;
using Xunit;
namespace Microsoft.BotBuilderSamples.Tests.Bots
{
public class DialogAndWelcomeBotTests
{
[Fact]
public async Task ReturnsWelcomeCardOnConversationUpdate()
{
// Arrange
var mockRootDialog = SimpleMockFactory.CreateMockDialog<Dialog>(null, "mockRootDialog");
// TODO: do we need state here?
var memoryStorage = new MemoryStorage();
var sut = new DialogAndWelcomeBot<Dialog>(new ConversationState(memoryStorage), new UserState(memoryStorage), mockRootDialog.Object, null);
var conversationUpdateActivity = new Activity
{
Type = ActivityTypes.ConversationUpdate,
MembersAdded = new List<ChannelAccount>
{
new ChannelAccount { Id = "theUser" },
},
Recipient = new ChannelAccount { Id = "theBot" },
};
var testAdapter = new TestAdapter(Channels.Test);
// Act
// Note: it is kind of obscure that we need to use OnTurnAsync to trigger OnMembersAdded so we get the card
await testAdapter.ProcessActivityAsync(conversationUpdateActivity, sut.OnTurnAsync, CancellationToken.None);
var reply = testAdapter.GetNextReply();
// Assert
var m = (IMessageActivity)reply;
Assert.Equal(1, m.Attachments.Count);
Assert.Equal("application/vnd.microsoft.card.adaptive", m.Attachments.FirstOrDefault()?.ContentType);
}
}
}

Просмотреть файл

@ -0,0 +1,100 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.BotBuilderSamples.Tests.Framework;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Microsoft.BotBuilderSamples.Tests.Bots
{
public class DialogBotTests
{
[Fact]
public async Task LogsInformationToILogger()
{
// Arrange
var memoryStorage = new MemoryStorage();
var conversationState = new ConversationState(memoryStorage);
var userState = new UserState(memoryStorage);
var mockRootDialog = SimpleMockFactory.CreateMockDialog<Dialog>(null, "mockRootDialog");
var mockLogger = new Mock<ILogger<DialogBot<Dialog>>>();
mockLogger.Setup(x =>
x.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), null, It.IsAny<Func<object, Exception, string>>()));
// Run the bot
var sut = new DialogBot<Dialog>(conversationState, userState, mockRootDialog.Object, mockLogger.Object);
var testAdapter = new TestAdapter();
var testFlow = new TestFlow(testAdapter, sut);
await testFlow.Send("Hi").StartTestAsync();
// Assert that log was changed with the expected parameters
mockLogger.Verify(
x => x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.Is<object>(o => o.ToString() == "Running dialog with Message Activity."),
null,
It.IsAny<Func<object, Exception, string>>()),
Times.Once);
}
[Fact]
public async Task SavesTurnStateUsingMockWithVirtualSaveChangesAsync()
{
// Note: this test requires that SaveChangesAsync is made virtual in order to be able to create a mock.
var memoryStorage = new MemoryStorage();
var mockConversationState = new Mock<ConversationState>(memoryStorage)
{
CallBase = true,
};
var mockUserState = new Mock<UserState>(memoryStorage)
{
CallBase = true,
};
var mockRootDialog = SimpleMockFactory.CreateMockDialog<Dialog>(null, "mockRootDialog");
var mockLogger = new Mock<ILogger<DialogBot<Dialog>>>();
// Act
var sut = new DialogBot<Dialog>(mockConversationState.Object, mockUserState.Object, mockRootDialog.Object, mockLogger.Object);
var testAdapter = new TestAdapter();
var testFlow = new TestFlow(testAdapter, sut);
await testFlow.Send("Hi").StartTestAsync();
// Assert that SaveChangesAsync was called
mockConversationState.Verify(x => x.SaveChangesAsync(It.IsAny<TurnContext>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Once);
mockUserState.Verify(x => x.SaveChangesAsync(It.IsAny<TurnContext>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Once);
}
[Fact(Skip = "TODO: need to figure out how to implement this version of the test")]
public async Task SavesTurnStateUsingMemoryStorage()
{
// TODO: Figure out how to implement this test.
// Note: this doesn't require a virtual SaveChangesAsync and it manually inspects storage to ensure the save methods were called.
var memoryStorage = new MemoryStorage();
var conversationState = new ConversationState(memoryStorage);
var userState = new UserState(memoryStorage);
var mockRootDialog = new Mock<Dialog>("mockRootDialog");
mockRootDialog.Setup(x => x.ContinueDialogAsync(It.IsAny<DialogContext>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(new DialogTurnResult(DialogTurnStatus.Empty)));
var mockLogger = new Mock<ILogger<DialogBot<Dialog>>>();
// Run the bot
var sut = new DialogBot<Dialog>(conversationState, userState, mockRootDialog.Object, mockLogger.Object);
var testAdapter = new TestAdapter();
var testFlow = new TestFlow(testAdapter, sut);
await testFlow.Send("Hi").StartTestAsync();
// Assert that SaveChangesAsyncWasCalled
Assert.True(false, "TODO");
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Moq;
using Xunit;
namespace Microsoft.BotBuilderSamples.Tests.Controllers
{
public class BotControllerTests
{
[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, arg => mockBot.Object)
{
ControllerContext = new ControllerContext(actionContext),
};
// Invoke the controller
await sut.PostAsync("doesn't matter");
// 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);
}
}
}

Просмотреть файл

@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit.Abstractions;
namespace Microsoft.BotBuilderSamples.Tests
{
/// <summary>
/// A base class with helper methods and properties to test dialogs in isolation.
/// </summary>
public abstract class DialogTestsBase
{
private static readonly Lazy<IConfiguration> _configurationLazy = new Lazy<IConfiguration>(() =>
{
LoadLaunchSettingsIntoEnvVariables("Properties//launchSettings.json");
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
return config.Build();
});
protected DialogTestsBase()
: this(null)
{
}
protected DialogTestsBase(ITestOutputHelper output)
{
Output = output;
}
public virtual IConfiguration Configuration => _configurationLazy.Value;
protected ITestOutputHelper Output { get; }
/// <summary>
/// Test runners to load environment variables defined in launchSettings.json
/// so this helper code loads it manually if the file is present.
/// This is useful to be able to have your own key files in your local machine without
/// having to put them in git.
/// If you use launch settings, make sure you set the Copy to Output Directory property to Copy Always.
/// </summary>
/// <param name="launchSettingsFile">The relative path to the launch settings file (i.e.: "Properties//launchSettings.json")</param>
private static void LoadLaunchSettingsIntoEnvVariables(string launchSettingsFile)
{
if (!File.Exists(launchSettingsFile))
{
return;
}
using (var file = File.OpenText(launchSettingsFile))
{
var reader = new JsonTextReader(file);
var fileData = JObject.Load(reader);
var variables = fileData
.GetValue("profiles")
.SelectMany(profiles => profiles.Children())
.SelectMany(profile => profile.Children<JProperty>())
.Where(prop => prop.Name == "environmentVariables")
.SelectMany(prop => prop.Value.Children<JProperty>())
.ToList();
foreach (var variable in variables)
{
Environment.SetEnvironmentVariable(variable.Name, variable.Value.ToString());
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Testing;
using Microsoft.Bot.Builder.Testing.XUnit;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Microsoft.BotBuilderSamples.Services;
using Microsoft.BotBuilderSamples.Tests.Dialogs.TestData;
using Microsoft.BotBuilderSamples.Tests.Framework;
using Moq;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs
{
public class BookingDialogTests : DialogTestsBase
{
private readonly XUnitOutputMiddleware[] _middlewares;
public BookingDialogTests(ITestOutputHelper output)
: base(output)
{
_middlewares = new[] { new XUnitOutputMiddleware(output) };
}
[Theory]
[MemberData(nameof(BookingDialogTestsDataGenerator.BookingFlows), MemberType = typeof(BookingDialogTestsDataGenerator))]
public async Task DialogFlowUseCases(TestDataObject testData)
{
// Arrange
var bookingTestData = testData.GetObject<BookingDialogTestCase>();
var mockFlightBookingService = new Mock<IFlightBookingService>();
mockFlightBookingService
.Setup(x => x.BookFlight(It.IsAny<BookingDetails>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(bookingTestData.FlightBookingServiceResult));
var mockGetBookingDetailsDialog = SimpleMockFactory.CreateMockDialog<GetBookingDetailsDialog>(bookingTestData.GetBookingDetailsDialogResult).Object;
var sut = new BookingDialog(mockGetBookingDetailsDialog, mockFlightBookingService.Object);
var testClient = new DialogTestClient(Channels.Test, sut, middlewares: _middlewares);
// Act/Assert
Output.WriteLine($"Test Case: {bookingTestData.Name}");
for (var i = 0; i < bookingTestData.UtterancesAndReplies.GetLength(0); i++)
{
var message = bookingTestData.UtterancesAndReplies[i, 0];
IMessageActivity reply;
if (!string.IsNullOrEmpty(message))
{
reply = await testClient.SendActivityAsync<IMessageActivity>(message);
}
else
{
reply = testClient.GetNextReply<IMessageActivity>();
}
Assert.Equal(bookingTestData.UtterancesAndReplies[i, 1], reply.Text);
}
Assert.Equal(bookingTestData.ExpectedDialogResult.Status, testClient.DialogTurnResult.Status);
}
}
}

Просмотреть файл

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Testing;
using Microsoft.Bot.Builder.Testing.XUnit;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs
{
public class CancelAndHelpDialogTests : DialogTestsBase
{
private readonly XUnitOutputMiddleware[] _middlewares;
public CancelAndHelpDialogTests(ITestOutputHelper output)
: base(output)
{
_middlewares = new[] { new XUnitOutputMiddleware(output) };
}
[Theory]
[InlineData("hi", "Hi there", "cancel")]
[InlineData("hi", "Hi there", "quit")]
public async Task ShouldBeAbleToCancel(string utterance, string response, string cancelUtterance)
{
var sut = new TestCancelAndHelpDialog();
var testClient = new DialogTestClient(Channels.Test, sut, middlewares: _middlewares);
var reply = await testClient.SendActivityAsync<IMessageActivity>(utterance);
Assert.Equal(response, reply.Text);
Assert.Equal(DialogTurnStatus.Waiting, testClient.DialogTurnResult.Status);
reply = await testClient.SendActivityAsync<IMessageActivity>(cancelUtterance);
Assert.Equal("Cancelling", reply.Text);
Assert.Equal(DialogTurnStatus.Cancelled, testClient.DialogTurnResult.Status);
}
[Theory]
[InlineData("hi", "Hi there", "help")]
[InlineData("hi", "Hi there", "?")]
public async Task ShouldBeAbleToGetHelp(string utterance, string response, string cancelUtterance)
{
var sut = new TestCancelAndHelpDialog();
var testClient = new DialogTestClient(Channels.Test, sut, middlewares: _middlewares);
var reply = await testClient.SendActivityAsync<IMessageActivity>(utterance);
Assert.Equal(response, reply.Text);
Assert.Equal(DialogTurnStatus.Waiting, testClient.DialogTurnResult.Status);
reply = await testClient.SendActivityAsync<IMessageActivity>(cancelUtterance);
Assert.Equal("Show Help...", reply.Text);
Assert.Equal(DialogTurnStatus.Waiting, testClient.DialogTurnResult.Status);
}
/// <summary>
/// A concrete instance of <see cref="CancelAndHelpDialog"/> for testing.
/// </summary>
private class TestCancelAndHelpDialog : CancelAndHelpDialog
{
public TestCancelAndHelpDialog()
: base(nameof(TestCancelAndHelpDialog))
{
AddDialog(new TextPrompt(nameof(TextPrompt)));
var steps = new WaterfallStep[]
{
PromptStep,
FinalStep,
};
AddDialog(new WaterfallDialog("testWaterfall", steps));
InitialDialogId = "testWaterfall";
}
private async Task<DialogTurnResult> PromptStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("Hi there") }, cancellationToken);
}
private Task<DialogTurnResult> FinalStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Testing;
using Microsoft.Bot.Builder.Testing.XUnit;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Microsoft.BotBuilderSamples.Tests.Dialogs.TestData;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs
{
public class DateResolverDialogTests : DialogTestsBase
{
public DateResolverDialogTests(ITestOutputHelper output)
: base(output)
{
}
[Theory]
[MemberData(nameof(DateResolverDialogTestsDataGenerator.DateResolverCases), MemberType = typeof(DateResolverDialogTestsDataGenerator))]
public async Task DialogFlowTests(TestDataObject testData)
{
// Arrange
var testCaseData = testData.GetObject<DateResolverDialogTestCase>();
var sut = new DateResolverDialog();
var testClient = new DialogTestClient(Channels.Test, sut, testCaseData.InitialData, new[] { new XUnitOutputMiddleware(Output) });
// Act/Assert
Output.WriteLine($"Test Case: {testCaseData.Name}");
Output.WriteLine($"\r\nDialog Input: {testCaseData.InitialData}");
for (var i = 0; i < testCaseData.UtterancesAndReplies.GetLength(0); i++)
{
var reply = await testClient.SendActivityAsync<IMessageActivity>(testCaseData.UtterancesAndReplies[i, 0]);
Assert.Equal(testCaseData.UtterancesAndReplies[i, 1], reply?.Text);
}
Output.WriteLine($"\r\nDialog result: {testClient.DialogTurnResult.Result}");
Assert.Equal(testCaseData.ExpectedResult, testClient.DialogTurnResult.Result);
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Testing;
using Microsoft.Bot.Builder.Testing.XUnit;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Microsoft.BotBuilderSamples.Tests.Dialogs.TestData;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs
{
public class GetBookingDetailsDialogTests : DialogTestsBase
{
public GetBookingDetailsDialogTests(ITestOutputHelper output)
: base(output)
{
}
[Theory]
[MemberData(nameof(GetBookingDetailsDialogTestsDataGenerator.BookingFlows), MemberType = typeof(GetBookingDetailsDialogTestsDataGenerator))]
[MemberData(nameof(GetBookingDetailsDialogTestsDataGenerator.CancelFlows), MemberType = typeof(GetBookingDetailsDialogTestsDataGenerator))]
public async Task DialogFlowUseCases(TestDataObject testData)
{
// Arrange
var bookingTestData = testData.GetObject<GetBookingDetailsDialogTestCase>();
var sut = new GetBookingDetailsDialog();
var testClient = new DialogTestClient(Channels.Test, sut, bookingTestData.InitialBookingDetails, new[] { new XUnitOutputMiddleware(Output) });
// Act/Assert
Output.WriteLine($"Test Case: {bookingTestData.Name}");
for (var i = 0; i < bookingTestData.UtterancesAndReplies.GetLength(0); i++)
{
var reply = await testClient.SendActivityAsync<IMessageActivity>(bookingTestData.UtterancesAndReplies[i, 0]);
Assert.Equal(bookingTestData.UtterancesAndReplies[i, 1], reply?.Text);
}
if (testClient.DialogTurnResult.Status == DialogTurnStatus.Cancelled)
{
Assert.Null(testClient.DialogTurnResult.Result);
}
else
{
var bookingResults = (BookingDetails)testClient.DialogTurnResult.Result;
Assert.Equal(bookingTestData.ExpectedBookingDetails.Origin, bookingResults.Origin);
Assert.Equal(bookingTestData.ExpectedBookingDetails.Destination, bookingResults.Destination);
Assert.Equal(bookingTestData.ExpectedBookingDetails.TravelDate, bookingResults.TravelDate);
}
}
}
}

Просмотреть файл

@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.BotBuilderSamples.Services;
using Microsoft.BotBuilderSamples.Tests.Framework;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs
{
/// <summary>
/// This sample uses the current classes and approach for testing bot conversations.
/// Note: this is included just as a reference.
/// </summary>
public class MainDialogTestFlowTests : DialogTestsBase
{
[Fact(Skip = "Ignoring this one, this is just a sample on the old way of writing tests")]
public async Task WholeEnchilada()
{
var mockFlightBookingService = new Mock<IFlightBookingService>();
mockFlightBookingService.Setup(x => x.BookFlight(It.IsAny<BookingDetails>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
var mockBookingDialog = SimpleMockFactory.CreateMockDialog<BookingDialog>(null, mockFlightBookingService.Object).Object;
var mockLogger = new Mock<ILogger<MainDialog>>();
var sut = new MainDialog(mockLogger.Object, null, mockBookingDialog);
var testFlow = BuildTestFlow(sut);
await testFlow.Send("hi")
.AssertReply("What can I help you with today?")
.Send("hi")
.AssertReply("Where would you like to travel to?")
.Send("Bahamas")
.AssertReply("Where are you traveling from?")
.Send("New York")
.AssertReply("When would you like to travel?")
.Send("tomorrow at 5 PM")
.AssertReply(activity =>
{
// TODO: I had to add the Yes No for the channelId = test, the emulator displays suggested actions instead.
var message = (IMessageActivity)activity;
Assert.Equal(
"Please confirm, I have you traveling to: Bahamas from: New York on: 2019-04-18T17 (1) Yes or (2) No",
message.Text);
})
.Send("Yes")
.AssertReply("I have you booked to Bahamas from New York on tomorrow 5PM")
.StartTestAsync();
}
private static TestFlow BuildTestFlow(Dialog targetDialog)
{
var convoState = new ConversationState(new MemoryStorage());
var testAdapter = new TestAdapter()
.Use(new AutoSaveStateMiddleware(convoState));
var dialogState = convoState.CreateProperty<DialogState>("DialogState");
var testFlow = new TestFlow(testAdapter, async (turnContext, cancellationToken) =>
{
var state = await dialogState.GetAsync(turnContext, () => new DialogState(), cancellationToken);
var dialogs = new DialogSet(dialogState);
dialogs.Add(targetDialog);
var dc = await dialogs.CreateContextAsync(turnContext, cancellationToken);
var results = await dc.ContinueDialogAsync(cancellationToken);
switch (results.Status)
{
case DialogTurnStatus.Empty:
await dc.BeginDialogAsync(targetDialog.Id, null, cancellationToken);
break;
case DialogTurnStatus.Complete:
{
// TODO: Dialog has ended, figure out a way of asserting that this is the case.
break;
}
}
});
return testFlow;
}
}
}

Просмотреть файл

@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Testing;
using Microsoft.Bot.Builder.Testing.XUnit;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Microsoft.BotBuilderSamples.CognitiveModels;
using Microsoft.BotBuilderSamples.Services;
using Microsoft.BotBuilderSamples.Tests.Framework;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs
{
public class MainDialogTests : DialogTestsBase
{
private readonly BookingDialog _mockBookingDialog;
private readonly Mock<IRecognizer> _mockLuisRecognizer;
private readonly Mock<ILogger<MainDialog>> _mockLogger;
public MainDialogTests(ITestOutputHelper output)
: base(output)
{
_mockLogger = new Mock<ILogger<MainDialog>>();
_mockLuisRecognizer = new Mock<IRecognizer>();
var mockFlightBookingService = new Mock<IFlightBookingService>();
mockFlightBookingService.Setup(x => x.BookFlight(It.IsAny<BookingDetails>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
_mockBookingDialog = SimpleMockFactory.CreateMockDialog<BookingDialog>(null, new Mock<GetBookingDetailsDialog>().Object, mockFlightBookingService.Object).Object;
}
[Fact]
public void DialogConstructor()
{
// TODO: check with the team if there's value in these types of test or if there's a better way of asserting the
// dialog got composed properly.
var sut = new MainDialog(_mockLogger.Object, _mockLuisRecognizer.Object, _mockBookingDialog);
Assert.Equal("MainDialog", sut.Id);
Assert.IsType<TextPrompt>(sut.FindDialog("TextPrompt"));
Assert.NotNull(sut.FindDialog("BookingDialog"));
Assert.IsType<WaterfallDialog>(sut.FindDialog("WaterfallDialog"));
}
[Fact]
public async Task ShowsMessageIfLuisNotConfigured()
{
// Arrange
var sut = new MainDialog(_mockLogger.Object, null, _mockBookingDialog);
var testClient = new DialogTestClient(Channels.Test, sut, middlewares: new[] { new XUnitOutputMiddleware(Output) });
// Act/Assert
var reply = await testClient.SendActivityAsync<IMessageActivity>("hi");
Assert.Equal("NOTE: LUIS is not configured. To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' to the appsettings.json file.", reply.Text);
reply = testClient.GetNextReply<IMessageActivity>();
Assert.Equal("What can I help you with today?", reply.Text);
}
[Fact]
public async Task ShowsPromptIfLuisIsConfigured()
{
// Arrange
var sut = new MainDialog(_mockLogger.Object, _mockLuisRecognizer.Object, _mockBookingDialog);
var testClient = new DialogTestClient(Channels.Test, sut, middlewares: new[] { new XUnitOutputMiddleware(Output) });
// Act/Assert
var reply = await testClient.SendActivityAsync<IMessageActivity>("hi");
Assert.Equal("What can I help you with today?", reply.Text);
}
[Theory]
[InlineData("I want to book a flight", "BookFlight", "BookingDialog mock invoked")]
[InlineData("What's the weather like?", "GetWeather", "TODO: get weather flow here")]
[InlineData("bananas", "None", "Sorry, I didn't get that. Please try asking in a different way (intent was None)")]
public async Task TaskSelector(string utterance, string intent, string invokedDialogResponse)
{
_mockLuisRecognizer
.Setup(x => x.RecognizeAsync<FlightBooking>(It.IsAny<ITurnContext>(), It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(new FlightBooking
{
Intents = new Dictionary<FlightBooking.Intent, IntentScore>
{
{ Enum.Parse<FlightBooking.Intent>(intent), new IntentScore() { Score = 1 } },
},
Entities = new FlightBooking._Entities(),
}));
var sut = new MainDialog(_mockLogger.Object, _mockLuisRecognizer.Object, _mockBookingDialog);
var testClient = new DialogTestClient(Channels.Test, sut, middlewares: new[] { new XUnitOutputMiddleware(Output) });
var reply = await testClient.SendActivityAsync<IMessageActivity>("hi");
Assert.Equal("What can I help you with today?", reply.Text);
reply = await testClient.SendActivityAsync<IMessageActivity>(utterance);
Assert.Equal(invokedDialogResponse, reply.Text);
reply = testClient.GetNextReply<IMessageActivity>();
Assert.Equal("What else can I do for you?", reply.Text);
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.Bot.Builder.Dialogs;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs.TestData
{
public class BookingDialogTestCase
{
/// <summary>
/// Gets or sets the name for the test case.
/// </summary>
/// <value>The test case name.</value>
public string Name { get; set; }
public BookingDetails GetBookingDetailsDialogResult { get; set; }
public string[,] UtterancesAndReplies { get; set; }
public DialogTurnResult ExpectedDialogResult { get; set; }
public bool BookedSuccessfully { get; set; }
public bool FlightBookingServiceResult { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Testing.XUnit;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs.TestData
{
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "Ignoring to make code more readable")]
public class BookingDialogTestsDataGenerator
{
public static IEnumerable<object[]> BookingFlows()
{
yield return BuildTestCaseObject(
"Full flow",
new BookingDetails
{
Destination = "Seattle",
Origin = "New York",
TravelDate = $"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
},
new[,]
{
{ "hi", "GetBookingDetailsDialog mock invoked" },
{ null, $"Please confirm, I have you traveling to: Seattle from: New York on: {DateTime.UtcNow.AddDays(1):yyyy-MM-dd} (1) Yes or (2) No" },
{ "yes", "Booking your flight, this shouldn't take long..." },
{ null, "I have you booked to Seattle from New York on tomorrow" },
});
yield return BuildTestCaseObject(
"Full flow with 'no' at confirmation",
new BookingDetails
{
Destination = "Seattle",
Origin = "New York",
TravelDate = $"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
},
new[,]
{
{ "hi", "GetBookingDetailsDialog mock invoked" },
{ null, $"Please confirm, I have you traveling to: Seattle from: New York on: {DateTime.UtcNow.AddDays(1):yyyy-MM-dd} (1) Yes or (2) No" },
{ "no", "OK, we can do this later" },
});
yield return BuildTestCaseObject(
"Full flow with 'cancel' at confirmation",
new BookingDetails
{
Destination = "Seattle",
Origin = "New York",
TravelDate = $"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
},
new[,]
{
{ "hi", "GetBookingDetailsDialog mock invoked" },
{ null, $"Please confirm, I have you traveling to: Seattle from: New York on: {DateTime.UtcNow.AddDays(1):yyyy-MM-dd} (1) Yes or (2) No" },
{ "cancel", "Cancelling" },
},
true,
new DialogTurnResult(DialogTurnStatus.Cancelled));
yield return BuildTestCaseObject(
"Full flow with failed call to FlightBookingService",
new BookingDetails
{
Destination = "Seattle",
Origin = "New York",
TravelDate = $"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
},
new[,]
{
{ "hi", "GetBookingDetailsDialog mock invoked" },
{ null, $"Please confirm, I have you traveling to: Seattle from: New York on: {DateTime.UtcNow.AddDays(1):yyyy-MM-dd} (1) Yes or (2) No" },
{ "yes", "Booking your flight, this shouldn't take long..." },
{ null, "Sorry, I was unable to secure your reservation, please try another flight" },
}, false);
}
private static object[] BuildTestCaseObject(string testCaseName, BookingDetails inputBookingInfo, string[,] utterancesAndReplies, bool flightBookingServiceResult = true, DialogTurnResult expectedDialogTurnResult = null)
{
var testData = new BookingDialogTestCase
{
Name = testCaseName,
GetBookingDetailsDialogResult = inputBookingInfo,
UtterancesAndReplies = utterancesAndReplies,
FlightBookingServiceResult = flightBookingServiceResult,
ExpectedDialogResult = expectedDialogTurnResult ?? new DialogTurnResult(DialogTurnStatus.Complete),
};
return new object[] { new TestDataObject(testData) };
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
namespace Microsoft.BotBuilderSamples.Tests.Dialogs.TestData
{
public class DateResolverDialogTestCase
{
/// <summary>
/// Gets or sets the name for the test case.
/// </summary>
/// <value>The test case name.</value>
public string Name { get; set; }
public string InitialData { get; set; }
public string ExpectedResult { get; set; }
public string[,] UtterancesAndReplies { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Bot.Builder.Testing.XUnit;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs.TestData
{
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "Ignoring to make code more readable")]
public class DateResolverDialogTestsDataGenerator
{
public static IEnumerable<object[]> DateResolverCases()
{
yield return BuildTestCaseObject(
"tomorrow",
null,
$"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
new[,]
{
{ "hi", "When would you like to travel?" },
{ "tomorrow", null },
});
yield return BuildTestCaseObject(
"the day after tomorrow",
null,
$"{DateTime.UtcNow.AddDays(2):yyyy-MM-dd}",
new[,]
{
{ "hi", "When would you like to travel?" },
{ "the day after tomorrow", null },
});
yield return BuildTestCaseObject(
"two days from now",
null,
$"{DateTime.UtcNow.AddDays(2):yyyy-MM-dd}",
new[,]
{
{ "hi", "When would you like to travel?" },
{ "two days from now", null },
});
yield return BuildTestCaseObject(
"valid input given (tomorrow)",
$"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
$"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
new[,]
{
{ "hi", null },
});
yield return BuildTestCaseObject(
"retry prompt",
null,
$"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
new[,]
{
{ "hi", "When would you like to travel?" },
{ "bananas", "I'm sorry, to make your booking please enter a full travel date including Day Month and Year." },
{ "tomorrow", null },
});
yield return BuildTestCaseObject(
"fuzzy time ",
null,
$"2055-05-05",
new[,]
{
{ "hi", "When would you like to travel?" },
{ "may 5th", "I'm sorry, to make your booking please enter a full travel date including Day Month and Year." },
{ "may 5th 2055", null },
});
}
private static object[] BuildTestCaseObject(string testCaseName, string input, string result, string[,] utterancesAndReplies)
{
var testData = new DateResolverDialogTestCase
{
Name = testCaseName,
InitialData = input,
ExpectedResult = result,
UtterancesAndReplies = utterancesAndReplies,
};
return new object[] { new TestDataObject(testData) };
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
namespace Microsoft.BotBuilderSamples.Tests.Dialogs.TestData
{
public class GetBookingDetailsDialogTestCase
{
/// <summary>
/// Gets or sets the name for the test case.
/// </summary>
/// <value>The test case name.</value>
public string Name { get; set; }
public BookingDetails InitialBookingDetails { get; set; }
public string[,] UtterancesAndReplies { get; set; }
public BookingDetails ExpectedBookingDetails { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Bot.Builder.Testing.XUnit;
namespace Microsoft.BotBuilderSamples.Tests.Dialogs.TestData
{
[SuppressMessage("Microsoft.StyleCop.CSharp.OrderingRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "Ignoring to make code more readable")]
public class GetBookingDetailsDialogTestsDataGenerator
{
public static IEnumerable<object[]> BookingFlows()
{
yield return BuildTestCaseObject(
"Full flow",
new BookingDetails(),
new[,]
{
{ "hi", "Where would you like to travel to?" },
{ "Seattle", "Where are you traveling from?" },
{ "New York", "When would you like to travel?" },
{ "tomorrow", null },
},
new BookingDetails
{
Destination = "Seattle",
Origin = "New York",
TravelDate = $"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
});
yield return BuildTestCaseObject(
"Destination given",
new BookingDetails
{
Destination = "Bahamas",
Origin = null,
TravelDate = null,
},
new[,]
{
{ "hi", "Where are you traveling from?" },
{ "New York", "When would you like to travel?" },
{ "tomorrow", null },
},
new BookingDetails
{
Destination = "Bahamas",
Origin = "New York",
TravelDate = $"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
});
yield return BuildTestCaseObject(
"Destination and Origin given",
new BookingDetails
{
Destination = "Seattle",
Origin = "New York",
TravelDate = null,
},
new[,]
{
{ "hi", "When would you like to travel?" },
{ "tomorrow", null },
},
new BookingDetails
{
Destination = "Seattle",
Origin = "New York",
TravelDate = $"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}",
});
yield return BuildTestCaseObject(
"All booking details given for today",
new BookingDetails
{
Destination = "Seattle",
Origin = "Bahamas",
TravelDate = $"{DateTime.UtcNow:yyyy-MM-dd}",
},
new[,]
{
{ "hi", null },
},
new BookingDetails
{
Destination = "Seattle",
Origin = "Bahamas",
TravelDate = $"{DateTime.UtcNow:yyyy-MM-dd}",
});
}
public static IEnumerable<object[]> CancelFlows()
{
yield return BuildTestCaseObject(
"Cancel on origin prompt",
new BookingDetails(),
new[,]
{
{ "hi", "Where would you like to travel to?" },
{ "cancel", "Cancelling" },
},
null);
yield return BuildTestCaseObject(
"Cancel on destination prompt",
new BookingDetails(),
new[,]
{
{ "hi", "Where would you like to travel to?" },
{ "Seattle", "Where are you traveling from?" },
{ "cancel", "Cancelling" },
},
null);
yield return BuildTestCaseObject(
"Cancel on date prompt",
new BookingDetails(),
new[,]
{
{ "hi", "Where would you like to travel to?" },
{ "Seattle", "Where are you traveling from?" },
{ "New York", "When would you like to travel?" },
{ "cancel", "Cancelling" },
},
null);
}
private static object[] BuildTestCaseObject(string testCaseName, BookingDetails inputBookingInfo, string[,] utterancesAndReplies, BookingDetails expectedBookingInfo)
{
var testData = new GetBookingDetailsDialogTestCase
{
Name = testCaseName,
InitialBookingDetails = inputBookingInfo,
UtterancesAndReplies = utterancesAndReplies,
ExpectedBookingDetails = expectedBookingInfo,
};
return new object[] { new TestDataObject(testData) };
}
}
}

Просмотреть файл

@ -0,0 +1,2 @@
# Note
This folder contains data generators and test case object definitions for Theory tests.

Просмотреть файл

@ -0,0 +1,2 @@
# Note
This folder contains a set of helper classes and extensions that could eventually be moved to the framework.

Просмотреть файл

@ -0,0 +1,43 @@
using System.Threading;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.AI.Luis;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Recognizers.Text;
using Moq;
namespace Microsoft.BotBuilderSamples.Tests.Framework
{
/// <summary>
/// Contains utility methods for creating simple mock objects based on <see href="http://stackoverflow.com">moq</see>/>.
/// </summary>
public static class SimpleMockFactory
{
// Creates a simple mock dialog.
public static Mock<T> CreateMockDialog<T>(object expectedResult = null, params object[] constructorParams)
where T : Dialog
{
var mockDialog = new Mock<T>(constructorParams);
var mockDialogNameTypeName = typeof(T).Name;
mockDialog
.Setup(x => x.BeginDialogAsync(It.IsAny<DialogContext>(), It.IsAny<object>(), It.IsAny<CancellationToken>()))
.Returns(async (DialogContext dialogContext, object options, CancellationToken cancellationToken) =>
{
await dialogContext.Context.SendActivityAsync($"{mockDialogNameTypeName} mock invoked", cancellationToken: cancellationToken);
return await dialogContext.EndDialogAsync(expectedResult, cancellationToken);
});
return mockDialog;
}
//public static Mock<T> CreateMockLuisRecognizer<T>(IRecognizerConvert returns)
// where T : LuisRecognizer
//{
// var mockRecognizer = new Mock<T>();
// mockRecognizer
// .Setup(x => x.RecognizeAsync<T>(It.IsAny<ITurnContext>(), It.IsAny<CancellationToken>()))
// .Returns(() => Task.FromResult(returns));
// return mockRecognizer;
//}
}
}

Просмотреть файл

@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
<AssemblyName>Microsoft.Bot.Builder.TestBot.Tests</AssemblyName>
<RootNamespace>Microsoft.BotBuilderSamples.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Remove="appsettings.json" />
<None Remove="xunit.runner.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.Testing\Microsoft.Bot.Builder.Testing.csproj" />
<ProjectReference Include="..\Microsoft.Bot.Builder.TestBot\Microsoft.Bot.Builder.TestBot.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Properties\launchSettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,24 @@
{
"cognitiveModels": {
"calendar": {
"luisAppId": "Set the value here or in launchSettings.json for local dev",
"luisEndpointKey": "Set the value here or in launchSettings.json for local dev",
"luisEndpoint": "Set the value here or in launchSettings.json for local dev"
},
"toDo": {
"luisAppId": "Set the value here or in launchSettings.json for local dev",
"luisEndpointKey": "Set the value here or in launchSettings.json for local dev",
"luisEndpoint": "Set the value here or in launchSettings.json for local dev"
},
"email": {
"luisAppId": "Set the value here or in launchSettings.json for local dev",
"luisEndpointKey": "Set the value here or in launchSettings.json for local dev",
"luisEndpoint": "Set the value here or in launchSettings.json for local dev"
},
"flightBooking": {
"luisAppId": "Set the value here or in launchSettings.json for local dev",
"luisEndpointKey": "Set the value here or in launchSettings.json for local dev",
"luisEndpoint": "Set the value here or in launchSettings.json for local dev"
}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"settings": {
"namingRules": {
"allowCommonHungarianPrefixes": true,
"allowedHungarianPrefixes": [
"lu"
]
}
}
}

Просмотреть файл

@ -81,7 +81,7 @@ namespace Microsoft.BotBuilderSamples
private async Task<DialogTurnResult> InvokeTaskStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var luisResult = await _luisRecognizer.RecognizeAsync<FlightBooking>(stepContext.Context, cancellationToken);
switch (luisResult.TopIntent().intent)
{
case FlightBooking.Intent.BookFlight:

Просмотреть файл

@ -113,6 +113,15 @@ namespace Microsoft.BotBuilderSamples
});
}
private static void RegisterDialogs(IServiceCollection services)
{
// Register booking dialog
services.AddSingleton(new BookingDialog(new GetBookingDetailsDialog(), new FlightBookingService()));
// The Dialog that will be run by the bot.
services.AddSingleton<MainDialog>();
}
private void RegisterLuisRecognizers(IServiceCollection services)
{
var luisIsConfigured = !string.IsNullOrEmpty(Configuration["LuisAppId"]) && !string.IsNullOrEmpty(Configuration["LuisAPIKey"]) && !string.IsNullOrEmpty(Configuration["LuisAPIHostName"]);
@ -127,14 +136,5 @@ namespace Microsoft.BotBuilderSamples
services.AddSingleton<IRecognizer>(recognizer);
}
}
private static void RegisterDialogs(IServiceCollection services)
{
// Register booking dialog
services.AddSingleton(new BookingDialog(new GetBookingDetailsDialog(), new FlightBookingService()));
// The Dialog that will be run by the bot.
services.AddSingleton<MainDialog>();
}
}
}