Home > Back-end >  Mock returns null when an object is expected
Mock returns null when an object is expected

Time:07-20

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.

  • Related