I have a test that looks like this.
namespace Domain.Tests.Unit.Features.ForUser.Auth
{
[TestClass]
public class LoginHandlerTests
{
[TestMethod]
public async Task Should_Succeede_With_Valid_User()
{
// Arrange
var loginCommand = new LoginHandler.LoginCommand
{
Email = "testemail",
Password = "testpassword"
};
var user = new User
{
Email = loginCommand.Email,
UserName = "testname",
};
var userServiceMock = new UserServiceFixture()
.WithSucceededFindByEmailAsync(loginCommand.Email)
.WithSucceededGetRolesAsync(user)
.GetMock();
// Problematic MOCK
var result = new Mock<ISignInServiceResult>()
.SetupProperty(l => l.Succeeded, true);
var signInServiceMock = new Mock<ISignInService>();
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(user, loginCommand.Password))
.Returns(Task.FromResult(result.Object));
var jwtGeneratorServiceMock = new JwtGeneratorServiceFixture()
.WithSucceededGeneration(loginCommand.Email, new string[] { "User" })
.GetMock();
// Here the result is what i expect
//var result1 = await signInServiceMock.Object.CheckPasswordSignInAsync(user, loginCommand.Password);
// Act
var sut = new LoginHandler.Handler(
userServiceMock.Object,
signInServiceMock.Object,
jwtGeneratorServiceMock.Object
);
var loginResponse = await sut.Handle(loginCommand, new CancellationToken());
// Assert
loginResponse.Should().NotBeNull();
loginResponse.Success.Should().BeTrue();
loginResponse.Token.Should().NotBeEmpty();
loginResponse.RefreshToken.Should().NotBeEmpty();
The only problem is that signInServiceMock returns null when the method is called on my handler. If I call it directly on my test i recieve the expected result. But when its called on my handler it always returns null. I have checked the setup method and the params needed, all seems correct. Any idea? Thanks The handler is this:
public class Handler : IRequestHandler<LoginCommand, LoginResponse>
{
private readonly IUserService _userService;
private readonly ISignInService _signInService;
private readonly IJwtGeneratorService _jwtGeneratorService;
public Handler(IUserService userService, ISignInService signInService, IJwtGeneratorService jwtGeneratorService)
{
_userService = userService;
_signInService = signInService;
_jwtGeneratorService = jwtGeneratorService;
}
public async Task<LoginResponse> Handle(LoginCommand command, CancellationToken _cancellationToken)
{
var user = await _userService.FindByEmailAsync(command.Email);
if (user is null) throw new InvalidLoginCredentialsException();
ISignInServiceResult checkedPassword
= await _signInService.CheckPasswordSignInAsync(user, command.Password);
if (!checkedPassword.Succeeded) throw new InvalidLoginCredentialsException();
var roles = await _userService.GetRolesAsync(user);
if (roles is null)
throw new UnableToGetRolesException();
ITokenData? token = _jwtGeneratorService.Generate(user.Email, roles);
if (token is null)
throw new LoginTokenGenerationException();
return new LoginResponse
{
Success = true,
Token = token.Token,
RefreshToken = token.RefreshToken
};
}
}
CodePudding user response:
The problem is that your mock setup is not correctly aligned with how your Handler
internally uses it.
In your setup you have this:
var user = new User
{
Email = loginCommand.Email,
UserName = "testname",
};
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(user, loginCommand.Password))
.Returns(Task.FromResult(result.Object));
That instructs the mock to return the desired result only upon receiving this exact user
instance, while internally in your Handler
you're creating another User
instance and passing it to the mocked method as follows:
// new User is being created:
var user = await _userService.FindByEmailAsync(command.Email);
// No longer matches your setup:
await _signInService.CheckPasswordSignInAsync(user, command.Password);
Perhaps you meant this, instead:
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(It.IsAny<User>(), loginCommand.Password))
Update:
Actually, the real problem probably lies in a missing setup of your UserService
mock.
Although you're calling .WithSucceededGetRolesAsync(user)
, I believe it doesn't affect the eventual call to FindByEmailAsync(user)
.
All in all, you have to make sure the call to FindByEmailAsync(user)
would indeed return the same user
instance in your setup.
CodePudding user response:
I believe what you want is:
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(user, loginCommand.Password))
.ReturnsAsync((User user, string password) => { return result.Object; });
rather than Returns
with a Task<T>
. Other than that, the code calling the CheckPasswordSignInAsync method may be passing a different argument value than your Mock is configured with, such as a Hash for the password. You can set a breakpoint and inspect that the values passed in actually match what your test configured.
Normally with Mocks you would configure to expect It.IsAny and assert the values using means like:
signInServiceMock.Setup(l => l.CheckPasswordSignInAsync(It.IsAny<User>(), It.IsAny<string>()))
.ReturnsAsync((User user, string password) => { return result.Object; });
from here the Returns will received the passed in values if it needs to perform any simple computation to mock out, or you can verify, such as that the user and password passed matched via:
signInServiceMock.Verify(l => l.CheckPasswordSignInAsync(It.Is<User>(u => u.EMail == "testemail"), It.Is<string>(p => p == "testpassword"));
This would at least fail with some description that the method was called, but the expected parameters didn't match.