NSubstitute
NSubstitute copied to clipboard
Get & set callback action result in extension method => dialogService.ShowDialog
Hi all!
Trying to mock an extension method that uses the the Prism Library DialogService with a callback action<IDialogResult>:
The NSub I attempted looked like this:
_subDialogService.When(x => x.ShowDialog(Arg.Is<string>, Arg.Is<IDialogParameters>, Arg.Invoke<Action<IDialogResult>>())).Callback<string, IDialogParameters, Action<IDialogResult>>((n, p, c) => c(new DialogResult(ButtonResult.OK)));
The following is the actual extension method being called in the application but it makes a call to the ShowDialog hence I am calling that in the above:
public static Task<DialogButtonResult> DisplayPromptDialogAsync(this IDialogService dialogService, string message, string? title = null, string? okButtonText = null, FontImageSource? fontImageSource = null)
{
TaskCompletionSource<DialogButtonResult> tcs = new();
try
{
dialogService.ShowDialog(PageNames.GenericPromptDialog, new DialogParameters
{
{ NavigationParameterKeys.Title, title },
{ NavigationParameterKeys.Message, message },
{ NavigationParameterKeys.OkButtonText, okButtonText },
{ NavigationParameterKeys.FontImageSource, fontImageSource }
}, (result) => tcs.SetResult(result.Parameters.GetValue<DialogButtonResult>("result")));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
The Prism Library ShowDialog being called:
public interface IDialogService
{
void ShowDialog(string name, IDialogParameters parameters, Action<IDialogResult> callback);
}
Finally the Logout functionality I need to test against is this which contains the extension method:
private async Task logoutAsync()
{
DialogButtonResult dialogButtonResult = await _dialogService.DisplayPromptDialogAsync(message: LocalizationResourceManager.Current.GetValue("SignOutMessage"), okButtonText: LocalizationResourceManager.Current.GetValue("SignOutText"), fontImageSource: new FontImageSource
{
Glyph = MaterialDesignIcons.Logout,
FontFamily = nameof(MaterialDesignIcons),
Color = Color.White,
Size = (OnPlatform<double>)PrismApplicationBase.Current.Resources["FontBodySize"]
}).ConfigureAwait(true);
if (dialogButtonResult is DialogButtonResult.OK && await _deepLinkingService.GoToLoginPageAsync(SignoutCustomerActionType.Complete).ConfigureAwait(false))
{
_eventAggregator.GetEvent<ApplicationInactivityTimerChangedEventArgs>().Publish(ApplicationInactivityTimerStatus.Stop);
}
}
Bassically I need to be able to unit test this method above by returning DialogButtonResult.OK and then I will do an assert like the following:
// Assert
_subEventAggregator.Received().Received().GetEvent<ApplicationInactivityTimerChangedEventArgs>().Publish(ApplicationInactivityTimerStatus.Stop);
An equivalent in MOQ I found was something like this but I have been struggling to translate this to NSubstitute (https://stackoverflow.com/questions/64770095/testing-prism-dialogservice):
// Arrange
var dialogServiceMock = new Mock<IDialogService>();
dialogServiceMock.Setup( x => x.ShowDialog( It.IsAny<string>(), It.IsAny<IDialogParameters>(), It.IsAny<Action<IDialogResult>>() ) )
.Callback<string, IDialogParameters, Action<IDialogResult>>( ( n, p, c ) => c( new DialogResult( ButtonResult.OK ) ) );
Currently giving an error on the Action<IDialogResult>
You expect that everyone can compile your code. It would be much easier to help, if you could provide a sample code.
If all you want is a substitute for IDialogService to call the action with your desired DialogResult,, you just have to write:
var dialogService = Substitute.For<IDialogService>();
dialogService.ShowDialog(Arg.Any<string>(), Arg.Any<IDialogParameters>(), Arg.Invoke<IDialogResult>(new DialogResult(ButtonResult.OK)));
Then any call like this, will have the desired result:
ButtonResult result = ButtonResult.None;
dialogService.ShowDialog("Test", null, r => result = r.Result);
result.Should().Be(ButtonResult.OK);
@GeraldLx I think I have discovered a problem, DialogResult was previously marked public but in the source code now it appears internal so not sure if I can proceed with this anymore unfortunately, looks like it had changed after that example I found using MOQ which gave me the impression it was possible but no way to call new DialogResult(DialogButtonResult.OK)
I still dont know which version you are talking about. I just pulled the latest PRISM NuGet packages.
I also dont understand why the change of DialogResult to internal is a problem. I mean the callback just wants an instance of IDialogResult. Basically that is what NSubstitute is designed for. After all you just want a result of ButonResult.OK. So if it is not public accessable, just create one:
var dialogService = Substitute.For<IDialogService>();
dialogService.WhenForAnyArgs(ds => ds.ShowDialog(default, default, default)).Do(c =>
{
var callback = c.Arg<Action<IDialogResult>>();
var dialogResult = Substitute.For<IDialogResult>();
dialogResult.Result.Returns(ButtonResult.OK);
callback(dialogResult);
});
Hope that helps.
I assume your question has been answered, if not, please let us know!
@304NotModified timely reminder that this issue did not get resolved unfortunately, checked now and getting the following issue:
As you can see from the following the parameters is null, not even sure there is some other underlying issue?
Unfortunately the code @GeraldLx provided did not compile but I made the change as follows:
My test case is the following:
[Fact]
public async Task ForceReAuthenticateInActiveCustomer_AuthCodeValid_HomeNavigationAsync()
{
// Arrange
IUserManagementService unitUnderTest = createService();
DialogParameters dialogParameters = new()
{
{
NavigationParameterKeys.AuthenticationEventType, AuthenticationEventType.InActivityTimeout
}
};
_subDialogService.WhenForAnyArgs(dialogService =>
dialogService.ShowDialog(PageNames.AuthenticationDialog, dialogParameters)).Do(callInfo
=>
{
Action<DialogResult> callback = callInfo.Arg<Action<DialogResult>>();
DialogResult dialogResult = new()
{
Parameters = new DialogParameters
{
{ NavigationParameterKeys.AuthenticationActionPerformed, AuthenticationResponseAction.None }
}
};
callback(dialogResult);
});
// Act
await unitUnderTest.ForceReAuthenticateInActiveCustomerAsync().ConfigureAwait(true);
// Assert
_ = _subDeepLinkingService.ReceivedCalls().Should().BeEmpty();
}
public static Task<AuthenticationResponseAction?> DisplayAuthenticationDialogAsync(this IDialogService dialogService, AuthenticationEventType authenticationEventType, INavigationParameters? navigationParameters = null)
{
TaskCompletionSource<AuthenticationResponseAction?> tcs = new();
try
{
DialogParameters dialogParameters = new()
{
{ NavigationParameterKeys.AuthenticationEventType, authenticationEventType }
};
if (navigationParameters is not null)
{
foreach (KeyValuePair<string, object> navigationParameter in navigationParameters)
{
dialogParameters.Add(navigationParameter.Key, navigationParameter.Value);
}
}
switch (authenticationEventType)
{
case AuthenticationEventType.InActivityTimeout:
dialogParameters.Add(KnownDialogParameters.CloseOnBackgroundTapped, false);
break;
case AuthenticationEventType.PreviouslySignedOut:
case AuthenticationEventType.AuthCodeExpired:
dialogParameters.Add(KnownDialogParameters.CloseOnBackgroundTapped, true);
break;
}
dialogService.ShowDialog(PageNames.AuthenticationDialog, dialogParameters, (result) => tcs.SetResult(result.Parameters.GetValue<AuthenticationResponseAction?>(NavigationParameterKeys.AuthenticationActionPerformed)));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
AuthenticationEventType has a value InActivityTimeout which if returned from the dialog I want to make sure none of the deeplinking methods are called. I apologize but providing any sample on this is not practical.
Also Prism DialogResult has Parameters get only so I have implemented my own class to put the setter on it as per this link so I can retrieve the parameters later.
public class DialogResult : IDialogResult
{
public Exception Exception => throw new NotImplementedException();
public IDialogParameters Parameters { get; set; }
}
Happy to close this now as I got this working!
Updated how the DialogService is resolved:
switch (await App.ContainerProvider.Resolve<IDialogService>()
.DisplayAuthenticationDialogAsync(AuthenticationEventType.InActivityTimeout)
.ConfigureAwait(true))
To this:
switch (await _dialogService.DisplayAuthenticationDialogAsync(AuthenticationEventType.InActivityTimeout)
.ConfigureAwait(true))