This is a great use case for the Moq framework. For example, when you pay attention to a remote call, this service should be mocked. Ideally, you should try to mock various variable aspects so that you can easily and consistently provide a known set of inputs to test the expected set of outputs. Since you have not provided any source code, I will make some assumptions - share some sample code and the corresponding unit tests.
public class LoginResult { public string RedirectUrl { get; set; } public string FailureMsg { get; set; } public MonkeyBusiness AuthToken { get; set; } } [ HttpPost, AllowAnonymous, Route("api/[controller]/login") ] public async Task<LoginResult> Login( [FromBody] CredentialsModel credentials, [FromServices] ILogger logger, [FromServices] IAuthenticationModule authentication, [FromServices] IAuthClaimsPrincipalBuiler claimsPrincipalBuilder) { try { var result = await authentication.ExternalAuthenticateAsync(credentials); if (result.IsAuthenticated) { var principal = await claimsPrincipalBuilder.BuildAsync(credentials); await HttpContext.Authentication.SignInAsync("Scheme", principal); return new LoginResult { AuthToken = "Too much source to make up just for stackoverflow, " + "but I hope you now get the point..." }; } return new LoginResult { FailureMsg = "Invalid credentials." }; } catch (Exception ex) { logger.LogError(ex.Message, ex); return new LoginResult { FailureMsg = ex.Message }; } }
Then you can have unit tests like this in which you mock specific implementations of interfaces. In this case, you would mock the IAuthenticationModule and have several tests to ensure that, although the remote server returns your Login logic, it processes it as desired / expected. Mocking async material is very simple.
using xUnit; [Fact] public async Task LoginThrowsAsExpectedTest() { // Arrange var controller = new LoginController(); var loggerMock = new Mock<ILogger>(); var authModuleMock = new Mock<IAuthenticationModule>(); var expectedMessage = "I knew it!"; // Setup async methods! authModuleMock.Setup(am => am.ExternalAuthenticateAsync(It.IsAny<CredentialsModel>()) .ThrowsAsync(new Exception(expectedMessage)); // Act var result = await controller.Login( new CredentialsModel(), loggerMock.Object, authModuleMock.Object, null); // Assert Assert.Equal(expectedMessage, result.FailureMsg); }
Or the one you expect to authenticate correctly.
using xUnit; [Fact] public async Task LoginHandlesIsNotAuthenticatedTest() { // Arrange var controller = new LoginController(); var loggerMock = new Mock<ILogger>(); var authModuleMock = new Mock<IAuthenticationModule>(); var expectedMessage = "Invalid credentials."; // Setup async methods! authModuleMock.Setup(am => am.ExternalAuthenticateAsync(It.IsAny<CredentialsModel>()) .ReturnsAsync(new AuthResult { IsAuthenticated = false }); // Act var result = await controller.Login( new CredentialsModel(), loggerMock.Object, authModuleMock.Object, null); // Assert Assert.Equal(expectedMessage, result.FailureMsg); }
The thing is, you can still write async and await unit tests using Moq quite easily ...