Skip confirmation step with MicrosoftBot's FormBuilder - c#

I don't need any confirmation before completion of my form. However, in the following Build() method of the FormBuilder class there is a Confirm("Is this your selection?\n{}")*.
public IForm<T> Build()
{
if (!_form._steps.Any((step) => step.Type == StepType.Field))
{
var paths = new List<string>();
FormBuilder<T>.FieldPaths(typeof(T), "", paths);
IFormBuilder<T> builder = this;
foreach (var path in paths)
{
builder.Field(new FieldReflector<T>(path));
}
builder.Confirm("Is this your selection?\n{*}");
}
Validate();
return this._form;
}
Is there any way I can remove this step from my generated Form after calling build ?
var form = new FormBuilder<QuestionYourThinking>()
.OnCompletionAsync(async (context, state) =>
{
await context.PostAsync("L'exercice est maintenant terminé. A bientot !");
})
.Build();

Just use the overload that take an ActiveDelegate parameter and make the method handler to return false then the confirmation message will not be shown.
return new FormBuilder<QuestionYourThinking>()
.AddRemainingFields()
.Confirm("No verification will be shown", state => false)
.Message("L'exercice est maintenant terminé. A bientot !")
.Build();
To send a message you can just use the fluent method Message.

You can just use .AddRemainingFields() on your FormBuilder. It will not ask for any confirmation.
.Confirm should be used when you want to add custom confirmation message for any specific field.

Related

Hide "No Preference" button only in the change prompt

I would like to remove the "No Preference" only in the "Change prompt" in the formflow or at least change it text only for the confirmation prompt leaving the of the form with "No Preference" option.
I was able to change its text, but it changed the whole form and didn't work for me.
public static IForm<PromoBot> BuildForm()
var form = new FormBuilder<PromoBot>()
.Message("Hi......!")
.Field(nameof(Nome), validate: async (state, value) =>
{
..........
result.IsValid = true;
return result;
}, active: state => true)
.Message("xxxxxx")
.Field(nameof(CEP)
.Field(nameof(Estados))
.Confirm(async (state) =>
{
return new PromptAttribute("You have selected the following: \n {*} "Is this correct?{||}");
})
.Message("Excelente! Agora vou precisar de alguns segundos para encontrar o melhor plano para sua empresa… já volto!")
.OnCompletion(OnComplete);
var noPreferenceStrings = new string[] { "New Text" };
form.Configuration.Templates.Single(t => t.Usage == TemplateUsage.NoPreference).Patterns = noPreferenceStrings;
form.Configuration.NoPreference = noPreferenceStrings;
return form.Build();
}
Ordinarily you can use a template attribute to modify FormFlow's behavior, but the navigation step is sort of finicky. I think the best thing to do in this situation is provide your form with a custom prompter.
In your case, the code could look something like this:
public static IForm<MyClass> BuildForm()
{
var formBuilder = new FormBuilder<MyClass>()
.Field(nameof(FirstName))
.Field(nameof(LastName))
.Confirm("Is this okay? {*}")
.Prompter(PromptAsync)
;
return formBuilder.Build();
}
/// <summary>
/// Here is the method we're using for the PromptAsyncDelgate.
/// </summary>
private static async Task<FormPrompt> PromptAsync(IDialogContext context, FormPrompt prompt,
MyClass state, IField<MyClass> field)
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
// Check to see if the form is on the navigation step
if (field.Name.Contains("navigate") && prompt.Buttons.Any())
{
// If it's on the navigation step,
// we want to change or remove the No Preference line
if (you_want_to_change_it)
{
var noPreferenceButton = prompt.Buttons.Last();
// Make sure the Message stays the same or else
// FormFlow won't know what to do when this button is clicked
noPreferenceButton.Message = noPreferenceButton.Description;
noPreferenceButton.Description = "Back";
}
else if(you_want_to_remove_it)
{
prompt.Buttons.RemoveAt(prompt.Buttons.Count - 1);
}
}
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
await context.PostAsync(promptMessage);
return prompt;
}
One additional note: "Back" is actually a special command in FormFlow. Whereas "No Preference" will take you back to the confirmation step, "Back" will take you to the last field in the form. If you want to actually put a back button in your navigation step, you can leave out this line:
noPreferenceButton.Message = noPreferenceButton.Description;

How to prevent original prompt from showing on re-validation in a FormFlow?

BuildForm Method
public static IForm<FAQConversation> BuildForm()
{
return new FormBuilder<FAQConversation>()
.Field(new FieldReflector<FAQConversation>(nameof(Inquiry))
.SetValidate(AnswerInquiry)
.SetPrompt(new PromptAttribute("Okay, tell me what is your question. Enter \"back\" to go back to Products Selection."))
)
.Build();
}
Validation Method
private static async Task<ValidateResult> AnswerInquiry(FAQConversation state, object value)
{
var result = new ValidateResult();
//somecode here
if(testCase == false)
{
result.isValid = false;
result.Feedback = "Try again";
}
else
{
result.isValid = true;
}
return result;
}
My validation method returns the feedback "Try Again" text when the input on my validating field is invalid. However, it is returning both Original Prompt and the Feedback text.
Question
How do I remove the original prompt on revalidation of a field?
While FormFlow does offer a lot of customizability, the main idea behind it is to automate everything for you, which tends to indicate that at least some things are built in pretty strongly.
I understand that what you want to do is disable the prompt for a field upon "retry," which is to say that if the user was already shown the prompt for a field and they entered something invalid then they shouldn't be shown the prompt again. I can see in the source code that FormFlow doesn't really provide a special case for "retries" and the behavior of prompting when a field remains unknown is one of those built-in things. However, there is still something you can do.
FormFlow offers a (largely undocumented) way to replace what's called the "prompter." You can do this using the Prompter() method, which takes a PromptAsyncDelegate. As a starting point for your new prompter, you can find the default prompter in the FormBuilder source code:
_form._prompter = async (context, prompt, state, field) =>
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
await context.PostAsync(promptMessage);
return prompt;
};
Whereas the default prompter always posts promptMessage, your replacement can surround that line with an if statement. That leaves the question of what your condition should be. We've established that FormFlow doesn't include any concept of a retry, so you'd have to build that in yourself somehow. You could include a Boolean field as a switch in FAQConversation's state, or you could even use PrivateConversationData since the prompter gives you access to the DialogContext. You might think that would be a simple matter of turning off the switch when the prompt gets displayed once or when AnswerInquiryAsync determines that the user input is invalid, but then when would the switch get turned back on? What if the user enters "back" and you want the prompt to be displayed again?
While you might find some way to more accurately represent the logic of "disabling the prompt on retry," the simplest solution I came up with was to keep track of the last message FormFlow produced and then skip the first message that comes after "Try again." It looks like this:
[Serializable]
public class FAQConversation
{
public string Inquiry { get; set; }
private string LastMessage { get; set; }
private const string TRY_AGAIN = "Try again";
public static IForm<FAQConversation> BuildForm()
{
return new FormBuilder<FAQConversation>()
// This is an alternative way of using the Field() method but it works the same.
.Field(nameof(Inquiry),
"Okay, tell me what is your question. Enter \"back\" to go back to Products Selection.",
validate: AnswerInquiryAsync)
.Prompter(PromptAsync)
.Build();
}
private static async Task<ValidateResult> AnswerInquiryAsync(FAQConversation state, object value)
{
var result = new ValidateResult();
bool testCase = Equals(value, "true"); // Enter "true" to continue for testing purposes.
if (testCase == false)
{
result.IsValid = false;
// A constant should be used with strings that appear more than once in your code.
result.Feedback = TRY_AGAIN;
}
else
{
result.IsValid = true;
// A value must be provided or else the Field will not be populated.
result.Value = value;
}
return result;
}
/// <summary>
/// Here is the method we're using for the PromptAsyncDelegate.
/// </summary>
private static async Task<FormPrompt> PromptAsync(IDialogContext context, FormPrompt prompt,
FAQConversation state, IField<FAQConversation> field)
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
// Here is where we've made a change to the default prompter.
if (state.LastMessage != TRY_AGAIN)
{
await context.PostAsync(promptMessage);
}
state.LastMessage = promptMessage.Text;
return prompt;
}
}

Moq Callback not working with 3 parameter method

I'm trying to understand why the following unit test does not execute the callback. If I modify the code so that the UpdateWorklowInstanceState method only contains 2 parameters (Guid and IList), it works. However, something about having 3 parameters interferes.
What I mean by interferes is that the Callback doesn't appear to get executed. There's no error message. I expect to see the "Error Occurred" message but instead receive an "Element Updated" message which means the Callback did not populate the resultMessages with the NotificationMessage.
public void BusinessObjectReturnsErrorNotification_ReturnErrorMessage()
{
var workflowInstanceGuid = Guid.NewGuid();
var workflowElementModel = new WorkflowElementModel
{
ElementName = "ValidName",
WorkflowInstanceId = workflowInstanceGuid.ToString()
};
var workflowElementInstance = new WorkflowElementInstance
{
ElementName = workflowElementModel.ElementName,
FullDescription = "Full Description",
SummaryDescription = "Summary Description",
RefTime = DateTime.Now,
ElementType = "ElementType"
};
var mockWebApiBusinessObject = new Mock<IWebApiBusinessObject>();
mockWebApiBusinessObject.Setup(m => m.UpdateWorkflowInstanceState(workflowInstanceGuid, workflowElementInstance, It.IsAny<List<NotificationMessage>>()))
.Callback<Guid, WorkflowElementInstance, IList<NotificationMessage>>(
(workflowInstanceId, elementDetails, resultMessages) =>
{
resultMessages.Add(new NotificationMessage("An Error Occured!", MessageSeverity.Error));
});
var controller = new WorkflowElementController(mockWebApiBusinessObject.Object);
var result = controller.UpdateWorkflowElement(workflowElementModel);
Assert.AreEqual("An Error Occured!", result.Content.ReadAsStringAsync().Result);
}
Method under test:
[HttpPost]
[ActionName("UpdateWorkflowElement")]
public HttpResponseMessage UpdateWorkflowElement(WorkflowElementModel workflowElementModel)
{
if (!ModelState.IsValid || workflowElementModel == null)
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
var response = new HttpResponseMessage(HttpStatusCode.OK);
string responseMessage;
if (workflowElementModel.RefTime == DateTime.MinValue)
{
workflowElementModel.RefTime = DateTime.UtcNow;
}
var resultMessages = new List<NotificationMessage>();
var instanceId = new Guid();
if (string.IsNullOrWhiteSpace(workflowElementModel.WorkflowInstanceId) ||
string.IsNullOrWhiteSpace(workflowElementModel.ElementName))
{
responseMessage = "WorkflowInstanceId or ElementName are null or empty";
}
else if (!Guid.TryParse(workflowElementModel.WorkflowInstanceId, out instanceId))
{
responseMessage = "WorkflowInstanceId is not a valid Guid";
}
else
{
var element = new WorkflowElementInstance
{
ElementName = workflowElementModel.ElementName,
RefTime = workflowElementModel.RefTime,
SummaryDescription = workflowElementModel.SummaryDescription ?? "",
FullDescription = workflowElementModel.FullDescription ?? ""
};
_webApiBusinessObject.UpdateWorkflowInstanceState(instanceId, element, resultMessages);
responseMessage = "Element Updated";
}
if (NotificationMessage.HasErrors(resultMessages))
{
responseMessage = resultMessages.Find(m => m.Status == MessageSeverity.Error).Message;
}
response.Content = new StringContent(responseMessage);
return response;
}
It does not work in you case for 3 parameters because you are mixing the expression parameter types.
It.IsAny<List<NotificationMessage>>()
in the setup, as apposed to the
IList<NotificationMessage>
in the callback parameters.
That means the setup expression parameters does not match the callback expression parameters so the call back is not going to be called.
Stick with one type so either go with the List<NotificationMessage> for both
You are also creating new instances of the parameters in the method under test, which would be different instance to the ones used in the setup. That is why the call back is not working. To prove it. Use It.IsAny<>() for all the parameters and it should work
mockWebApiBusinessObject
.Setup(m => m.UpdateWorkflowInstanceState(It.IsAny<Guid>(), It.IsAny<WorkflowElementInstance>(), It.IsAny<List<NotificationMessage>>()))
.Callback<Guid, WorkflowElementInstance, List<NotificationMessage>>(
(workflowInstanceId, elementDetails, resultMessages) =>
{
resultMessages.Add(new NotificationMessage("An Error Occured!", MessageSeverity.Error));
});
Or the more generic interface
mockWebApiBusinessObject
.Setup(m => m.UpdateWorkflowInstanceState(It.IsAny<Guid>(), It.IsAny<WorkflowElementInstance>(), It.IsAny<IList<NotificationMessage>>()))
.Callback<Guid, WorkflowElementInstance, IList<NotificationMessage>>(
(workflowInstanceId, elementDetails, resultMessages) =>
{
resultMessages.Add(new NotificationMessage("An Error Occured!", MessageSeverity.Error));
});
You should also take some time and review Moq Quickstart to get a better understanding of how to use the mocking framework.
Please consider updating at minor places in your unit test.
Add before mocking IWebApiBusinessObject object:
List<NotificationMessage> messages = new List<NotificationMessage>();
Additionally, update Callback :
var mock = new Mock<IWebApiBusinessObject>();
mock.
Setup(m => m.UpdateWorkflowInstanceState(It.IsNotNull<Guid>(), It.IsNotNull<WorkflowElementInstance>(),It.IsAny<List<NotificationMessage>>() )).
Callback(() =>
{
messages.Add(new NotificationMessage("error msg", MessageSeverity.Severe));
messages.Add(new NotificationMessage("Ignore Message", MessageSeverity.Normal)); // this is optional... u can remove it if u want.
});
And need to update the source code method UpdateWorkflowElement(WorkflowElementModel model) to
UpdateWorkflowElement(WorkflowElementModel model, List<NotificationMessage> messages);
Consider changes in unit test code calling UpdateWorkflowElement to
var result = controller.UpdateWorkflowElement(workflowElementModel, messages);
If I have understood your UpdateWorkflowInstanceState() method correctly,
then you are using IWebApiBusinessObject to call UpdateWorkflowInstanceState( , , ) method.
When UpdateWorkflowInstanceState( , , ) executes during unit testing, it fires the Callback in your unit test and adds messages in list of NotificationMessage.

how to assert if a method has been called using nunit

is it possible to assert whether a method has been called? I'm testing the following method and I want to assert that the _tokenManager.GetToken() has been called. I just want to know if the method has been called as the method does not return a value. I am using Moq.
Thanks,
Code snippet
public void Subscribe(string code, string emailAddress, string columnKey)
{
// Request authentication token
var token = _tokenManager.GetToken(code, false);
if (!_tokenValidator.Validate(token))
{
// Token has expired or invalid - refresh the token
token = _tokenManager.GetToken(code, true);
}
// Subscribe email
_silverpopRepository.Subscribe(token.AccessToken, emailAddress, columnKey);
}
You should mock TokenManager and TokenValidator, and then create two unit test cases:
Case 1: token is validated and GetToken is called exactly once
Case 2: token is not validated and GetToken is called exactly twice
Case 1:
[Test]
public void Subscribe_TokenIsValidated_GetTokenIsCalledOnce()
{
// Arrange:
var tokenManagerMock = Mock.Of<TokenManager>();
var tokenValidatorMock = Mock.Of<TokenValidator>(x =>
x.Validate(It.IsAny<Token>()) == true);
var subscriber = new Subscriber
{
TokenManager = tokenManagerMock,
TokenValidator = tokenValidatorMock
};
// Act:
subscriber.Subscribe(It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>());
// Assert:
Mock.Get(tokenManagerMock).Verify(x =>
x.GetToken(It.IsAny<string>(), It.IsAny<bool>()), Times.Once);
}
Case 2:
[Test]
public void Subscribe_TokenIsExpiredOrInvalid_GetTokenIsCalledTwice()
{
// Arrange:
var tokenManagerMock = Mock.Of<TokenManager>();
var tokenValidatorMock = Mock.Of<TokenValidator>(x =>
x.Validate(It.IsAny<Token>()) == false);
var subscriber = new Subscriber
{
TokenManager = tokenManagerMock,
TokenValidator = tokenValidatorMock
};
// Act:
subscriber.Subscribe(It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>());
// Assert:
Mock.Get(tokenManagerMock).Verify(x =>
x.GetToken(It.IsAny<string>(), It.IsAny<bool>()), Times.Exactly(2));
}
Alternatively, you can create an unit test without mocking TokenValidator and verify if GetToken() has been called at least once. However, creating two cases as in the first example is preferred as we are testing all code paths.
// Arrange:
var tokenManagerMock = Mock.Of<TokenManager>();
var subscriber = new Subscriber {TokenManager = tokenManagerMock};
// Act:
subscriber.Subscribe(It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>());
// Assert:
Mock.Get(tokenManagerMock).Verify(x =>
x.GetToken(It.IsAny<string>(), It.IsAny<bool>()), Times.AtLeastOnce);
Read more about verification in Moq at:
Moq quick start at official website
Verifying the Number of Calls to a Mocked Method at BlackWasp
You can verify using MOQ using the Verify method. Like this:
var tokenManagerMock = new Mock<ITokenManager>();
var sut = new WhateverItIsCalled(tokenManagerMock.Object);
sut.Subscribe("ssss", "example#example.com", "XXX");
tokenManagerMock.Verify(m => m.GetToken(It.Is<string>(c => c == "ssss", It.Is<bool>(x => x == false)), Times.Once);
You need to be able to pass the token manager into your system under test somehow. Usually via the ctor or maybe a property.
I would suggest you use something like AutoFixture to remove the ugliness that is "ssss" and make things a bit more DRY.
You may need to make the token manager mock return something appropriate too that will pass the validation. Something like this:
var tokenManagerMock = new Mock<ITokenManager>();
tokenManagerMock.Setup(m => m.GetToken(It.Is<string>(x => x == "ssss", It.IsAny<bool>()).Returns("XXXXXX");

Testing ReactiveCommand async result

I use Xamarin and ReactiveUI to do mobile apps.
I'd like to test my ViewModel and its ReactiveCommand.
I register an asynchronous operation which return an IObservable inside the ReactiveCommand.
In ViewModel constructor :
Login = new ReactiveCommand();
var loginResult = Login.RegisterAsync(_ => Client.Login(Username, Password));
loginResult.ObserveOn(RxApp.MainThreadScheduler).BindTo(this, self => self.ConnectedUser);
Login
.Where(_ => ConnectedUser != null)
.Subscribe(_ => {
ConnectedUser.Disconnect();
ConnectedUser = null;
});
What's in Client.Login :
return Observable.Start(async () => {
// Do an HTTP POST to login the user, with await stuff in
// Return the User on success or throw an exception on error
}, RxApp.TaskpoolScheduler)
.Select(task => task.Result);
How to test the ViewModel login success ? Here is my approach which doesn't work :
[Test]
public void TestLoginSuccess()
{
ViewModel viewModel = new ViewModel();
viewModel.Username = "toto";
viewModel.Password = "****";
viewModel.Login.Execute(null);
Assert.That(viewModel.ConnectedUser, Is.Not.Null); // always null so fail :(
}
The test fails because the assertion is made before the command has finished executing.
Resolution
I was subscribing to the Login command and assumed that my "disconnection block" would be called before the RegisterAsync's one. Inverted the two would resolve the issue or simply put the "disconnection" logic inside RegisterAsync like this :
var loginResult = Login.RegisterAsync(_ => {
if (ConnectedUser != null)
{
ConnectedUser.Disconnect();
ConnectedUser = null;
}
Client.Login(Username, Password);
});
Paul Betts ' solution was also necessary.
TaskPoolScheduler is not set to Scheduler.Immediate, so it really is running in the background. Try this:
Scheduler.CurrentThread.With(sched => {
// TODO: Write your code here, now both schedulers are
// rigged to CurrentThread.
});

Categories