Comment procéder à un test unitaire de robots

S'APPLIQUE À : SDK v4

Dans cette rubrique, nous vous expliquons comment :

  • Créez des tests unitaires sur des bots.
  • Utilisez l'assertion pour vérifier les activités retournées par une boîte de dialogue par rapport à des valeurs attendues.
  • Utilisez l'assertion pour vérifier les résultats retournés par une boîte de dialogue.
  • Créez différents types de tests pilotés par les données.
  • Créez des objets fictifs pour les différentes dépendances d'un dialogue, telles que les dispositifs de reconnaissance de la langue, etc.

Prérequis

L'exemple Tests CoreBot utilisé dans cette rubrique fait référence au package Microsoft.Bot.Builder.Testing, XUnit et Moq pour créer des tests unitaires.

L'échantillon de bot principal utilise la compréhension du langage (LUIS) pour identifier les intentions utilisateur ; toutefois, l'identification de l'intention de l'utilisateur n'est pas le but de cet article. Pour plus d'informations sur l'identification des intentions utilisateur, consultez La compréhension du langage naturel et ajoutez la compréhension du langage naturel à votre bot.

Remarque

Compréhension du langage (LUIS) sera mis hors service le 1er octobre 2025. À compter du 1er avril 2023, vous ne pourrez pas créer de nouvelles ressources LUIS. Une version plus récente de Compréhension du langage est désormais disponible dans le cadre d'Azure AI Language.

Compréhension du langage conversationnel (CLU), une fonctionnalité d'Azure AI Language, est la version mise à jour de LUIS. Pour plus d'informations sur la prise en charge de compréhension du langage dans le kit de développement logiciel (SDK) Bot Framework, consultez Compréhension du langage naturel.

Tests des boîtes de dialogue

Dans l'échantillon CoreBot, les boîtes de dialogue sont testées par unité à partir de la classe DialogTestClient qui fournit un mécanisme permettant de les tester de manière isolée en dehors d'un bot, sans avoir à déployer votre code sur un service Web.

À l’aide de cette classe, vous pouvez écrire des tests unitaires qui valident les réponses de boîtes de dialogue sur une base étape par étape. Les tests unitaires utilisant la classe DialogTestClient doivent fonctionner avec d’autres boîtes de dialogue créées à l’aide de la bibliothèque de boîtes de dialogue botbuilder.

L’exemple suivant illustre les tests dérivés de DialogTestClient :

var sut = new BookingDialog();
var testClient = new DialogTestClient(Channels.Msteams, sut);

var reply = await testClient.SendActivityAsync<IMessageActivity>("hi");
Assert.Equal("Where would you like to travel to?", reply.Text);

reply = await testClient.SendActivityAsync<IMessageActivity>("Seattle");
Assert.Equal("Where are you traveling from?", reply.Text);

reply = await testClient.SendActivityAsync<IMessageActivity>("New York");
Assert.Equal("When would you like to travel?", reply.Text);

reply = await testClient.SendActivityAsync<IMessageActivity>("tomorrow");
Assert.Equal("OK, I will book a flight from Seattle to New York for tomorrow, Is this Correct?", reply.Text);

reply = await testClient.SendActivityAsync<IMessageActivity>("yes");
Assert.Equal("Sure thing, wait while I finalize your reservation...", reply.Text);

reply = testClient.GetNextReply<IMessageActivity>();
Assert.Equal("All set, I have booked your flight to Seattle for tomorrow", reply.Text);

La classe DialogTestClient est définie dans l'espace de noms Microsoft.Bot.Builder.Testing et incluse dans le package NuGet Microsoft.Bot.Builder.Testing.

DialogTestClient

Le premier paramètre de DialogTestClient est le canal cible. Cela vous permet de tester différentes logiques de rendu en fonction de l'objectif du canal pour votre bot (Teams, Slack, etc.). Si vous n'êtes pas certain de votre canal cible, vous pouvez utiliser les ID de canal Emulator ou Test, mais gardez à l'esprit que certains composants peuvent se comporter différemment selon le canal actuel, par exemple, ConfirmPrompt rend les options Oui/Non différemment pour les canaux Test et Emulator. Vous pouvez également utiliser ce paramètre pour tester la logique de rendu conditionnel de test dans votre boîte de dialogue en fonction de l’ID du canal.

Le deuxième paramètre est une instance du dialogue en cours de test. Dans l'exemple de code de cet article, sut représente le système testé.

Le constructeur DialogTestClient fournit des paramètres supplémentaires qui vous permettent de personnaliser davantage le comportement du client ou de passer des paramètres à la boîte de dialogue en cours de test, si nécessaire. Vous pouvez transmettre des données d’initialisation pour la boîte de dialogue, ajouter un intergiciel personnalisé ou utiliser vos propres TestAdapter et instance ConversationState.

Envoi et réception des messages

La méthode SendActivityAsync<IActivity> vous permet d’envoyer un énoncé de SMS ou une IActivity à votre boîte de dialogue et retourne le premier message qu’il reçoit. Le paramètre <T> est utilisé pour retourner une instance fortement typée de la réponse afin que vous puissiez la déclarer sans avoir à la caster.

var reply = await testClient.SendActivityAsync<IMessageActivity>("hi");
Assert.Equal("Where would you like to travel to?", reply.Text);

Dans certains scénarios, votre robot peut envoyer plusieurs messages en réponse à une seule activité. Dans ces cas, DialogTestClient met en file d’attente les réponses et vous pouvez utiliser la méthode GetNextReply<IActivity> pour dépiler le message suivant de la file de réponse.

reply = testClient.GetNextReply<IMessageActivity>();
Assert.Equal("All set, I have booked your flight to Seattle for tomorrow", reply.Text);

GetNextReply<IActivity> retourne la valeur null s’il n’y a plus de messages dans la file de réponse.

Assertion des activités

Le code de l’exemple CoreBot déclare uniquement la propriété Text des activités retournées. Dans les bots plus complexes, vous souhaiterez peut-être déclarer d'autres propriétés telles que Speak, InputHint, ChannelData, etc.

Assert.Equal("Sure thing, wait while I finalize your reservation...", reply.Text);
Assert.Equal("One moment please...", reply.Speak);
Assert.Equal(InputHints.IgnoringInput, reply.InputHint);

Pour ce faire, vous pouvez vérifier chaque propriété individuellement comme indiqué ci-dessus, vous pouvez écrire vos propres utilitaires d’assistance pour déclarer des activités ou vous pouvez utiliser d'autres infrastructures comme FluentAssertions pour écrire des assertions personnalisées et simplifier votre code de test.

Transmission de paramètres vers votre boîte de dialogue

Le constructeur DialogTestClient a un initialDialogOptions qui peut être utilisé pour passer des paramètres à votre boîte de dialogue. Par exemple, le MainDialog de cet échantillon initialise un objet BookingDetails à partir des résultats de la reconnaissance linguistique, avec les entités qu'il résout à partir de l'énoncé de l'utilisateur, et transmet cet objet dans l'appel à l'invocation de BookingDialog.

Vous pouvez implémenter cela dans un test comme suit :

var inputDialogParams = new BookingDetails()
{
    Destination = "Seattle",
    TravelDate = $"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}"
};

var sut = new BookingDialog();
var testClient = new DialogTestClient(Channels.Msteams, sut, inputDialogParams);

BookingDialog reçoit ce paramètre et y accède dans le test de la même façon que lors de son appel à partir de MainDialog.

private async Task<DialogTurnResult> DestinationStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var bookingDetails = (BookingDetails)stepContext.Options;
    ...
}

L’assertion de la boîte de dialogue transformer les résultats

Certaines boîtes de dialogue telles que BookingDialog ou DateResolverDialog retournent une valeur à la boîte de dialogue appelante. L’objet DialogTestClient expose une propriété DialogTurnResult qui peut être utilisée pour analyser et déclarer les résultats retournés par la boîte de dialogue.

Par exemple :

var sut = new BookingDialog();
var testClient = new DialogTestClient(Channels.Msteams, sut);

var reply = await testClient.SendActivityAsync<IMessageActivity>("hi");
Assert.Equal("Where would you like to travel to?", reply.Text);

...

var bookingResults = (BookingDetails)testClient.DialogTurnResult.Result;
Assert.Equal("New York", bookingResults?.Origin);
Assert.Equal("Seattle", bookingResults?.Destination);
Assert.Equal("2019-06-21", bookingResults?.TravelDate);

La propriété DialogTurnResult peut également être utilisée pour inspecter et déclarer les résultats intermédiaires retournés par les étapes d’une cascade.

Analyse de la sortie de test

Il est parfois nécessaire de lire une transcription de test unitaire pour analyser l'exécution de test sans avoir à déboguer le test.

Le package Microsoft.Bot.Builder.Testing comprend un XUnitDialogTestLogger qui enregistre les messages envoyés et reçus par la boîte de dialogue dans la console.

Pour utiliser cet intergiciel, votre test doit exposer un constructeur qui reçoit un objet ITestOutputHelper fourni par le Test Runner XUnit et créer un XUnitDialogTestLogger qui sera passé à DialogTestClient par le biais du paramètre middlewares.

public class BookingDialogTests
{
    private readonly IMiddleware[] _middlewares;

    public BookingDialogTests(ITestOutputHelper output)
        : base(output)
    {
        _middlewares = new[] { new XUnitDialogTestLogger(output) };
    }

    [Fact]
    public async Task SomeBookingDialogTest()
    {
        // Arrange
        var sut = new BookingDialog();
        var testClient = new DialogTestClient(Channels.Msteams, sut, middlewares: _middlewares);

        ...
    }
}

Voici un exemple de la façon dont XUnitDialogTestLogger se connecte à la fenêtre de sortie lorsqu'il est configuré :

Example middleware output from XUnit.

Pour plus d’informations sur l’envoi d’une sortie de test à la console lors de l’utilisation de XUnit, consultez Capture de la sortie dans la documentation XUnit.

Cette sortie sera également consignée sur le serveur de builds pendant que l’intégration continue génère et vous aide à analyser les échecs de build.

Tests pilotés par les données

Dans la plupart des cas, la logique de la boîte de dialogue ne change pas et les différents chemins d’exécution d’une conversation sont basés sur les énoncés utilisateur. Plutôt que d'écrire un seul test unitaire pour chaque variante de la conversation, il est plus facile d'utiliser des tests pilotés par les données (également appelés tests paramétrables).

Par exemple, l'échantillon de test dans la section de vue d'ensemble de ce document montre comment tester un flux d'exécution, mais pas d'autres, tels que :

  • Que se passe-t-il si l'utilisateur dit non à la confirmation ?
  • Que se passe-t-il s'ils utilisent une date différente ?

Les tests pilotés par les données nous permettent de tester toutes ces permutations sans avoir à réécrire les tests.

Dans l’exemple CoreBot, nous utilisons des tests Theory de XUnit pour paramétrer des tests.

Tests de théorie utilisant InlineData

Le test suivant vérifie qu'une boîte de dialogue est annulée lorsque l'utilisateur dit « annuler ».

[Fact]
public async Task ShouldBeAbleToCancel()
{
    var sut = new TestCancelAndHelpDialog();
    var testClient = new DialogTestClient(Channels.Test, sut);

    var reply = await testClient.SendActivityAsync<IMessageActivity>("Hi");
    Assert.Equal("Hi there", reply.Text);
    Assert.Equal(DialogTurnStatus.Waiting, testClient.DialogTurnResult.Status);

    reply = await testClient.SendActivityAsync<IMessageActivity>("cancel");
    Assert.Equal("Cancelling...", reply.Text);
}

Pour annuler une boîte de dialogue, les utilisateurs peuvent saisir « quitter », « ignorer » et « arrêter ». Au lieu d'écrire un nouveau cas de test pour chaque mot possible, écrivez une méthode de test Theory unique qui accepte des paramètres via une liste de valeurs InlineData pour définir les paramètres de chaque cas de test :

[Theory]
[InlineData("cancel")]
[InlineData("quit")]
[InlineData("never mind")]
[InlineData("stop it")]
public async Task ShouldBeAbleToCancel(string cancelUtterance)
{
    var sut = new TestCancelAndHelpDialog();
    var testClient = new DialogTestClient(Channels.Test, sut, middlewares: _middlewares);

    var reply = await testClient.SendActivityAsync<IMessageActivity>("Hi");
    Assert.Equal("Hi there", reply.Text);
    Assert.Equal(DialogTurnStatus.Waiting, testClient.DialogTurnResult.Status);

    reply = await testClient.SendActivityAsync<IMessageActivity>(cancelUtterance);
    Assert.Equal("Cancelling...", reply.Text);
}

Le nouveau test sera exécuté quatre fois avec les différents paramètres et chaque cas sera affiché en tant qu'élément enfant sous le test ShouldBeAbleToCancel dans l'Explorateur de tests Visual Studio. Si l'un d'eux échoue, comme indiqué ci-dessous, vous pouvez cliquer avec le bouton de droite et déboguer le scénario qui a échoué au lieu de exécute à nouveau l'ensemble complet des tests.

Example test results for in-line data.

Tests de théorie utilisant des types MemberData et complexes

InlineData est utile pour les tests pilotés par les données de petite taille qui reçoivent des paramètres de type valeur simples (chaîne, int, etc.).

La BookingDetails reçoit un objet BookingDialog et retourne un nouvel objet BookingDetails. Une version non paramétrable d’un test pour cette boîte de dialogue doit se présenter comme suit :

[Fact]
public async Task DialogFlow()
{
    // Initial parameters
    var initialBookingDetails = new BookingDetails
    {
        Origin = "Seattle",
        Destination = null,
        TravelDate = null,
    };

    // Expected booking details
    var expectedBookingDetails = new BookingDetails
    {
        Origin = "Seattle",
        Destination = "New York",
        TravelDate = "2019-06-25",
    };

    var sut = new BookingDialog();
    var testClient = new DialogTestClient(Channels.Test, sut, initialBookingDetails);

    // Act/Assert
    var reply = await testClient.SendActivityAsync<IMessageActivity>("hi");
    ...

    var bookingResults = (BookingDetails)testClient.DialogTurnResult.Result;
    Assert.Equal(expectedBookingDetails.Origin, bookingResults?.Origin);
    Assert.Equal(expectedBookingDetails.Destination, bookingResults?.Destination);
    Assert.Equal(expectedBookingDetails.TravelDate, bookingResults?.TravelDate);
}

Pour paramétrer ce test, nous avons créé une classe BookingDialogTestCase qui contient les données de notre cas de test. Il contient l’objet BookingDetails initial, l’objet BookingDetails attendu et un tableau de chaînes contenant les énoncés envoyés par l’utilisateur et les réponses attendues de la boîte de dialogue pour chaque tour.

public class BookingDialogTestCase
{
    public BookingDetails InitialBookingDetails { get; set; }

    public string[,] UtterancesAndReplies { get; set; }

    public BookingDetails ExpectedBookingDetails { get; set; }
}

Nous avons également créé une classe BookingDialogTestsDataGenerator d’assistance exposant une méthode IEnumerable<object[]> BookingFlows() qui retourne une collection des cas de test à utiliser par le test.

Pour afficher chaque cas de test sous la forme d’un élément distinct dans l’Explorateur de tests Visual Studio, le Test Runner XUnit requiert que les types complexes tels que BookingDialogTestCase implémentent IXunitSerializable, pour simplifier cela, l'infrastructure Bot.Builder.Testing fournit un classe TestDataObject qui implémente cette interface et qui peut être utilisée pour inclure dans un wrapper les données de cas de test sans avoir à implémenter IXunitSerializable.

Voici un fragment de IEnumerable<object[]> BookingFlows() qui montre comment les deux classes sont utilisées :

public static class BookingDialogTestsDataGenerator
{
    public static IEnumerable<object[]> BookingFlows()
    {
        // Create the first test case object
        var testCaseData = new BookingDialogTestCase
        {
            InitialBookingDetails = new BookingDetails(),
            UtterancesAndReplies = new[,]
            {
                { "hi", "Where would you like to travel to?" },
                { "Seattle", "Where are you traveling from?" },
                { "New York", "When would you like to travel?" },
                { "tomorrow", $"Please confirm, I have you traveling to: Seattle from: New York on: {DateTime.Now.AddDays(1):yyyy-MM-dd}. Is this correct? (1) Yes or (2) No" },
                { "yes", null },
            },
            ExpectedBookingDetails = new BookingDetails
            {
                Destination = "Seattle",
                Origin = "New York",
                TravelDate = $"{DateTime.Now.AddDays(1):yyyy-MM-dd}",
            }, 
        };
        // wrap the test case object into TestDataObject and return it.
        yield return new object[] { new TestDataObject(testCaseData) };

        // Create the second test case object
        testCaseData = new BookingDialogTestCase
        {
            InitialBookingDetails = new BookingDetails
            {
                Destination = "Seattle",
                Origin = "New York",
                TravelDate = null,
            },
            UtterancesAndReplies = new[,]
            {
                { "hi", "When would you like to travel?" },
                { "tomorrow", $"Please confirm, I have you traveling to: Seattle from: New York on: {DateTime.Now.AddDays(1):yyyy-MM-dd}. Is this correct? (1) Yes or (2) No" },
                { "yes", null },
            },
            ExpectedBookingDetails = new BookingDetails
            {
                Destination = "Seattle",
                Origin = "New York",
                TravelDate = $"{DateTime.Now.AddDays(1):yyyy-MM-dd}",
            },
        };
        // wrap the test case object into TestDataObject and return it.
        yield return new object[] { new TestDataObject(testCaseData) };
    }
}

Une fois que nous avons créé un objet pour stocker les données de test et une classe qui expose une collection de cas de test, nous utilisons l’attribut MemberData de XUnit au lieu de InlineData pour alimenter les données dans le test, le premier paramètre pour MemberData est le nom de la fonction statique qui retourne la collection de cas de test et le deuxième paramètre est le type de classe qui expose cette méthode.

[Theory]
[MemberData(nameof(BookingDialogTestsDataGenerator.BookingFlows), MemberType = typeof(BookingDialogTestsDataGenerator))]
public async Task DialogFlowUseCases(TestDataObject testData)
{
    // Get the test data instance from TestDataObject
    var bookingTestData = testData.GetObject<BookingDialogTestCase>();
    var sut = new BookingDialog();
    var testClient = new DialogTestClient(Channels.Test, sut, bookingTestData.InitialBookingDetails);

    // Iterate over the utterances and replies array.
    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);
    }

    // Assert the resulting BookingDetails object
    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);
}

Voici un exemple des résultats des tests DialogFlowUseCases dans l'Explorateur de tests Visual Studio lorsque le test est exécuté :

Example results for the booking dialog.

Utilisation des objets fictifs

Vous pouvez utiliser des éléments fictifs pour tout ce qui n'est pas en cours de test. À titre de référence, ce niveau peut généralement être assimilé à un test d’unité et d’intégration.

En simulant autant d'éléments que vous le souhaitez, vous pouvez mieux isoler la partie à tester. Les éléments fictifs possibles sont le stockage, l'adaptateur, l'intergiciel, le pipeline d'activités, les canaux et tout ce qui ne fait pas directement partie de votre bot. Il est également possible d'impliquer la suppression temporaire de certains éléments, par exemple un intergiciel non concerné par ce que vous testez dans votre bot, et ce, afin d'isoler chaque partie. Si vous testez votre intergiciel, vous pouvez cependant vouloir simuler votre bot à la place.

La simulation d'éléments peut prendre une multitude de formes, depuis le remplacement d'un élément par un autre objet connu jusqu'à la mise en œuvre d'une fonctionnalité minimale de type « hello world ». L'élément peut aussi être retiré s'il n'est pas nécessaire ou s'il est forcé de rester inactif.

Les objets fictifs nous permettent de configurer les dépendances d'une boîte de dialogue et de s'assurer qu'elles sont dans un état connu pendant l'exécution du test sans avoir à s'appuyer sur des ressources externes comme des bases de données, des modèles de langage ou d'autres objets.

Pour faciliter le test de votre boîte de dialogue et réduire ses dépendances sur des objets externes, vous devrez peut-être injecter les dépendances externes dans le constructeur de boîte de dialogue.

Par exemple, au lieu d'instancier BookingDialog dans MainDialog :

public MainDialog()
    : base(nameof(MainDialog))
{
    ...
    AddDialog(new BookingDialog());
    ...
}

Nous passons une instance de BookingDialog en tant que paramètre de constructeur :

public MainDialog(BookingDialog bookingDialog)
    : base(nameof(MainDialog))
{
    ...
    AddDialog(bookingDialog);
    ...
}

Cela nous permet de remplacer l'instance BookingDialog par un objet fictif et d’écrire des tests unitaires pour la MainDialog sans avoir à appeler la classe BookingDialog réelle.

// Create the mock object
var mockDialog = new Mock<BookingDialog>();

// Use the mock object to instantiate MainDialog
var sut = new MainDialog(mockDialog.Object);

var testClient = new DialogTestClient(Channels.Test, sut);

Simulation de boîtes de dialogue

Comme décrit ci-dessus, MainDialog appelle BookingDialog pour obtenir l’objet BookingDetails. Nous implémentons et configurons une instance fictive de BookingDialog comme suit :

// Create the mock object for BookingDialog.
var mockDialog = new Mock<BookingDialog>();
mockDialog
    .Setup(x => x.BeginDialogAsync(It.IsAny<DialogContext>(), It.IsAny<object>(), It.IsAny<CancellationToken>()))
    .Returns(async (DialogContext dialogContext, object options, CancellationToken cancellationToken) =>
    {
        // Send a generic activity so we can assert that the dialog was invoked.
        await dialogContext.Context.SendActivityAsync($"{mockDialogNameTypeName} mock invoked", cancellationToken: cancellationToken);

        // Create the BookingDetails instance we want the mock object to return.
        var expectedBookingDialogResult = new BookingDetails()
        {
            Destination = "Seattle",
            Origin = "New York",
            TravelDate = $"{DateTime.UtcNow.AddDays(1):yyyy-MM-dd}"
        };

        // Return the BookingDetails we need without executing the dialog logic.
        return await dialogContext.EndDialogAsync(expectedBookingDialogResult, cancellationToken);
    });

// Create the sut (System Under Test) using the mock booking dialog.
var sut = new MainDialog(mockDialog.Object);

Dans cet exemple, nous avons utilisé Moq pour créer la boîte de dialogue fictive et les méthodes Setup et Returns pour configurer son comportement.

Résultats de la simulation LUIS

Remarque

Compréhension du langage (LUIS) sera mis hors service le 1er octobre 2025. À compter du 1er avril 2023, vous ne pourrez pas créer de nouvelles ressources LUIS. Une version plus récente de Compréhension du langage est désormais disponible dans le cadre d'Azure AI Language.

Compréhension du langage conversationnel (CLU), une fonctionnalité d'Azure AI Language, est la version mise à jour de LUIS. Pour plus d'informations sur la prise en charge de compréhension du langage dans le kit de développement logiciel (SDK) Bot Framework, consultez Compréhension du langage naturel.

Dans des scénarios simples, vous pouvez implémenter des résultats LUIS fictifs à l’aide du code comme suit :

var mockRecognizer = new Mock<IRecognizer>();
mockRecognizer
    .Setup(x => x.RecognizeAsync<FlightBooking>(It.IsAny<ITurnContext>(), It.IsAny<CancellationToken>()))
    .Returns(() =>
    {
        var luisResult = new FlightBooking
        {
            Intents = new Dictionary<FlightBooking.Intent, IntentScore>
            {
                { FlightBooking.Intent.BookFlight, new IntentScore() { Score = 1 } },
            },
            Entities = new FlightBooking._Entities(),
        };
        return Task.FromResult(luisResult);
    });

Les résultats LUIS peuvent être complexes. Lorsque c'est le cas, il est plus simple de capturer le résultat souhaité dans un fichier JSON, de l'ajouter en tant que ressource à votre projet et de le désérialiser en un résultat LUIS. Voici un exemple :

var mockRecognizer = new Mock<IRecognizer>();
mockRecognizer
    .Setup(x => x.RecognizeAsync<FlightBooking>(It.IsAny<ITurnContext>(), It.IsAny<CancellationToken>()))
    .Returns(() =>
    {
        // Deserialize the LUIS result from embedded json file in the TestData folder.
        var bookingResult = GetEmbeddedTestData($"{GetType().Namespace}.TestData.FlightToMadrid.json");

        // Return the deserialized LUIS result.
        return Task.FromResult(bookingResult);
    });

Informations supplémentaires