0

In my dialog I use a validator to check the users input. I have a line that looks like this:

AddDialog(new TextPrompt(nameof(AskForCustomerId), validators.TestValidator));

I also have a class which contains my validations for this dialog. The class looks like this:

public class MyValidator : IMyValidator
{
    public async Task<bool> TestValidator(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
    {
        // Do some validations...
        
        return true;
    }
}

I thought that it's a good idea to have my validation code separated in another class.

But now I'd like to write unit tests for this particular class. But how can I do this?

I tried the following, but I can't create an instance of PromptValidatorContext, because the constructor is internal...

[Fact]
public void Test()
{
    // Arrange
    var sut = new MyValidator(sessionManagerMock.Object);
    var prompt = new PromptValidatorContext(); // ctor is internal.. cant create instance.

    // Act
    sut.TestValidator( ) // How to pass params?

    // Assert
}

Anyone any idea how to unit test this?


Update

So I have a Dialog in which I inject my "Validator" class. My Dialog class looks like this:

public class MyDialog : BaseDialog
{

    public MyDialog(ICustomerValidator validator) : ComponentDialog
    {
        AddDialog(new WaterfallDialog(nameof(WaterfallDialog),
            new WaterfallStep[]
            {
                // ... my steps
            }));

        AddDialog(new TextPrompt(nameof(Step1Example)));
        AddDialog(new TextPrompt(nameof(Step2Example), validator.CustomerIdValidator));
        // etc.

        InitialDialogId = nameof(WaterfallDialog);
    }
}

I the code for validation in a separate class (hence the ICustomerValidator)

public class CustomerValidator : ICustomerValidator
{
    public async Task<bool> CustomerIdValidator(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
    {
        // example
        return promptContext.Context.Activity.Text == "100";
    }
}

This all works fine. But how do I write a unit test solely for the CustomerValidator class?

Vivendi
  • 20,047
  • 25
  • 121
  • 196
  • Have you had a look at the unit tests in the Bot Builder .NET repo? – Kyle Delaney Nov 06 '20 at 20:17
  • @KyleDelaney Yes I have, but couldn't find anything helpful. – Vivendi Nov 07 '20 at 11:44
  • Can you explain what you're trying to do that isn't already in PromptValidatorContextTests? https://github.com/microsoft/botbuilder-dotnet/blob/main/tests/Microsoft.Bot.Builder.Dialogs.Tests/PromptValidatorContextTests.cs – Kyle Delaney Nov 18 '20 at 00:57
  • Are you still working on this? – Kyle Delaney Nov 21 '20 at 00:52
  • @KyleDelaney Yes, I still have this issue. I've updated my question. If it's still unclear please let me know. Thanks. – Vivendi Nov 23 '20 at 16:12
  • When you say "solely for the `CustomerValidator` class" do you mean you'd only accept an answer that calls `CustomerIdValidator` directly in a test rather than through a prompt class in `TestFlow`? – Kyle Delaney Nov 23 '20 at 18:24
  • @KyleDelaney Yes. That's exactly what I'm looking for. I'm looking for an answer that calls `CustomerIdValidator` directly in a test. – Vivendi Nov 23 '20 at 18:36
  • Does this answer your question? [Instancing a class with an internal constructor](https://stackoverflow.com/questions/1199590/instancing-a-class-with-an-internal-constructor) – Kyle Delaney Nov 23 '20 at 19:30
  • @KyleDelaney Not really tbh... I need to setup a `PromptValidatorContext` in my test. Having to instantiate that in a "hacky" way is a bit of a design flaw, don't you think? I have tried this in my test: `var obj = (PromptValidatorContext)typeof(PromptValidatorContext).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null).Invoke(new object[]{tc.Object, prr, new Dictionary(), new PromptOptions()})` Gives a null ref exception on something that's propbably internal... – Vivendi Nov 24 '20 at 15:31
  • If there were some reason you *had* to instantiate `PromptValidatorContext` then not being able to do that would probably be a design flaw. But prompt validators are not designed to be called directly, and like many components of the dialogs library they're meant to be tested using test flow (or a dialog test client). You're trying to do a hacky thing and so you'll need to do it in a hacky way if you can do it at all. There is some more discussion of this design pattern here: https://stackoverflow.com/questions/1005149/is-there-a-way-to-derive-from-a-class-with-an-internal-constructor – Kyle Delaney Nov 24 '20 at 18:56
  • @KyleDelaney I guess I had different expectations on how to test the validators. I don't really agree on the way how it is implemented in the bot, but that's a whole other discussion. I have managed to test the validators by testing the dialog flows, like you said. It works, so I'll leave it like that. I think that is currently the best option. Thanks a lot for your patience and help. – Vivendi Nov 25 '20 at 08:00

1 Answers1

0

Prompt validators are not meant to be called directly, even in tests. They are meant to be tested indirectly using test flow as seen in PromptValidatorContextTests, or by using a dialog test client.

Kyle Delaney
  • 11,616
  • 6
  • 39
  • 66