Warm tip: This article is reproduced from serverfault.com, please click

How to unit test custom prompt that requires user select a choice in Bot Framework

发布于 2020-12-04 22:37:21

I have some custom prompts made with adaptive cards which require user inputs such as input.ChoiceSet and input.Text.

My problem here is if I have a card with 3 choices ('Red', 'Blue', 'Green'), and I would like to test the result of "Red" being selected. How do I send the choice and get result in my test code. I would like to unit test the result and flow after something is selected and the user hit the submit button.

I use JObject to get the user's selection as a string;

In the constructor of my dialog class I have add the prompt to the dialog.

AddDialog(new CustomPrompt("cardPrompt", DialogValidators.AdaptiveCardValidatorAsync));

Here is my dialog code for testing.

private async Task<DialogTurnResult> aStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
            var adaptiveActivity = stepContext.Context.Activity.CreateReply();
            adaptiveActivity.AddCardAttachment();
            return await stepContext.PromptAsync(
                "CardPrompt",
                new PromptOptions
                {
                    Prompt = adaptiveActivity,
                    RetryPrompt = MessageFactory.Text("xxx"),
                    Validations = new List<string> { "selectionCard" }
                });
}

private async Task<DialogTurnResult> bStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
            JObject answerObject = (JObject)JsonConvert.DeserializeObject(stepContext.Result.ToString());
            string userChoice = answerObject["choiceID"].ToString();
}

Below is my test code.

userState = new UserState(new MemoryStorage());
var subDialog = new SubDialog(userState);
var MainDialog = new MainDialog(subDialog);
var testClient = new DialogTestClient(Channels.Msteams, MainDialog);

// some code here

reply = await testClient.SendActivityAsync<IMessageActivity>("Red");
Assert.AreEqual(XXXX, reply....);

How to work with DialogTestClient for the test?

Appreciate your time for this.

Update

Below is my prompt class. It extends Prompt2 which has abstract methods (OnPromptAsync and OnRecognizeAsync) and extends Dialog class.


public class CustomPrompt : Prompt2<string>
    {
        public CustomPrompt(string dialogId, PromptValidator<string> validator = null)
            : base(dialogId, validator)
        {
        }

        protected async override Task OnPromptAsync(ITurnContext turnContext, IDictionary<string, object> state, PromptOptions options, bool isRetry, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (turnContext == null)
            {
                throw new ArgumentNullException(nameof(turnContext));
            }

            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            if (isRetry && options.RetryPrompt != null)
            {
                await turnContext.SendActivityAsync(options.RetryPrompt, cancellationToken).ConfigureAwait(false);
            }
            else if (options.Prompt != null)
            {
                await turnContext.SendActivityAsync(options.Prompt, cancellationToken).ConfigureAwait(false);
            }
        }

        protected override Task<PromptRecognizerResult<string>> OnRecognizeAsync(ITurnContext turnContext, IDictionary<string, object> state, PromptOptions options, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (turnContext == null)
            {
                throw new ArgumentNullException(nameof(turnContext));
            }

            var result = new PromptRecognizerResult<string>();
            if (turnContext.Activity.Type == ActivityTypes.Message)
            {
                var message = turnContext.Activity.AsMessageActivity();
                if (!string.IsNullOrEmpty(message.Text))
                {
                    result.Succeeded = true;
                    result.Value = message.Text;
                }

                /*Add handling for Value from adaptive card*/
                else if (message.Value != null)
                {
                    result.Succeeded = true;
                    result.Value = message.Value.ToString();
                }
            }

            return Task.FromResult(result);
        }
    }


Here is the structure of my card.

{
  "type": "AdaptiveCard",
  "body": [
    {
      "type": "Container",
      "items": [
        {
          "type": "Input.ChoiceSet",
          "wrap":  true,
          "id": "choiceID",
          "value": {defaultValue}
          "placeholder": "Placeholder text",
          "choices": [
            {
              title: "aaaa",
              value: "testChoice"
            }
          ],
          "style": "expanded"
        }
      ]
    }
  ],
  "actions": [
    {
      "type": "Action.Submit",
      "id": "submit",
      "title": "Confirm",
      "data": {
        "value": "submit",
        "id": selectionCard"
      }
    },
    {
      "type": "Action.Submit",
      "id": "cancel",
      "title": "Cancel",
      "data": {
        "value": "cancel",
        "id": "selectionCard"
      }
    }
  ],
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.1"

I was wondering how do I test if I select any choice of the card and get proper response from the bot using TestFlow. Thanks.

Questioner
RayGun
Viewed
0
Kyle Delaney 2020-12-25 08:53:38

The code in your prompt class reveals that Adaptive Card submit action data gets transferred to the bot in the activity's value property:

/*Add handling for Value from adaptive card*/
else if (message.Value != null)
{
    result.Succeeded = true;
    result.Value = message.Value.ToString();
}

All you must do in the test is to simulate sending such an activity by populating its value property yourself.

var activity = new Activity(ActivityTypes.Message, value: submitActionData);
reply = await testClient.SendActivityAsync<IMessageActivity>(activity);

This should work with either dialog test client or test flow. You can see some examples of this in my cards library tests.