Search code examples
c#unit-testingnunitasp.net-core-webapiargumentexception

Getting System.ArgumentException even while sending the right number of parameters while using Moq setup


While unit testing my ASP.NET web api controllers,for one of my post requests, I am sending two arguments in the repo function, Like usual I am mocking the repository setup, but even when I send two arguments, I get the argument exception.

Repo Code-

public async Task<TblUser> Register(TblUser user, string password)
    {
        byte[] passwordHash, passwordSalt;
        CreatePasswordHash(password, out passwordHash, out passwordSalt);
        user.APasswordHash = passwordHash;
        user.APasswordSalt = passwordSalt;
        await _context.TblUser.AddAsync(user);
        await _context.SaveChangesAsync();
        return user;
    }

Controller code-

[HttpPost("register")]
    public async Task<IActionResult> Register(UserForRegisterDto userForRegisterDto)
    {
        userForRegisterDto.AUsername = userForRegisterDto.AUsername.ToLower();

        if (await _repo.UserExists(userForRegisterDto.AUsername))
            return BadRequest("username already exists");

        if(!ModelState.IsValid || userForRegisterDto.AUsername == null || userForRegisterDto.Password == null || userForRegisterDto.AEmail ==null || userForRegisterDto.Aname ==null)
        {
            return BadRequest("invalid user");
        }

        var userToCreate = _mapper.Map<TblUser>(userForRegisterDto);

        var createdUser = await _repo.Register(userToCreate, userForRegisterDto.Password);

        var userToReturn = _mapper.Map<UserForDetailedDto>(createdUser);

        return CreatedAtRoute("GetUser", new
        {
            controller = "Users",
            id = createdUser.ACustomerId
        }, userToReturn
        );
    }

Controller Test code-

[Test]
    public async Task GivenAValidUser_WhenIRegisterANewUser_ThenItReturnsOkWithResponse()
    {
        _mockAuthRepository = new Mock<IAuthRepository>();
        _mockAuthMapper = new Mock<IMapper>();
        _mockConfig = new Mock<IConfiguration>();
        UserForRegisterDto expectedUser = new UserForRegisterDto
        {
            Aname = "Luffy",
            AUsername = "luffy",
            AEmail = "[email protected]",
            Password = "password",
            ADob = new DateTime(2000, 12, 12)
        };
        _mockAuthMapper.Setup(mapper => mapper.Map<TblUser>(It.IsAny<UserForRegisterDto>()))
            .Returns(new TblUser());
        _mockAuthMapper.Setup(mapper => mapper.Map<UserForDetailedDto>(It.IsAny<TblUser>()))
        .Returns(new UserForDetailedDto());
        _mockAuthRepository.Setup(repo => repo.Register(It.IsAny<TblUser>(), It.IsAny<string>()))
        .ReturnsAsync((TblUser user) => user);

        _authController = new AuthController(_mockAuthRepository.Object,_mockConfig.Object, _mockAuthMapper.Object);
        var tblUser = await _authController.Register(expectedUser);
        var okResult = tblUser as OkObjectResult;
        Assert.AreEqual(200, okResult.StatusCode);
        Assert.NotNull(okResult);
        Assert.IsAssignableFrom<OkObjectResult>(tblUser);
        var result = ((OkObjectResult)tblUser).Value;
        Assert.NotNull(result);
        Assert.AreEqual(expectedUser, result);
        Assert.IsAssignableFrom<TblUser>(result);


    }

The exception is thrown by the line-

            _mockAuthRepository.Setup(repo => repo.Register(It.IsAny<TblUser>(), It.IsAny<string>()))
    .ReturnsAsync((TblUser user) => user);

Like you can see, while setting up the mock repository, I am sending two arguments but I get the error-

Message: 
System.ArgumentException : Invalid callback. Setup on method with 2 parameter(s) cannot invoke callback with different number of parameters (1).

Stack Trace:

<>c__DisplayClass22_0 
<SetReturnComputedValueBehavior>g__ValidateCallback|4(Delegate callback)
MethodCall.SetReturnComputedValueBehavior(Delegate valueFactory)
NonVoidSetupPhrase`2.Returns[T1](Func`2 valueExpression)
GeneratedReturnsExtensions.ReturnsAsync[T,TMock,TResult]
(IReturns`2 mock, Func`2 valueFunction) AuthControllerTests.GivenAValidUser_WhenIRegisterANewUser_ThenItReturnsOkWithResponse() line 43
GenericAdapter`1.GetResult()
AsyncToSyncAdapter.Await(Func`1 invoke)
TestMethodCommand.RunTestMethod(TestExecutionContext context)
TestMethodCommand.Execute(TestExecutionContext context)
SimpleWorkItem.PerformWork()

I am new to unit testing but so far, my unit tests for GET and POST requests have been working fine this way. Is this an issue Hashing function I am calling inside?

Controller code that was working- [AllowAnonymous] [HttpPost()] public async Task AddMovie(MovieForDetailedDto movieForDetailedDto) { if (await _repo.MovieExists(movieForDetailedDto.ATitle)) return BadRequest("movie already exists");

        else if(!ModelState.IsValid || movieForDetailedDto.ATitle == null || movieForDetailedDto.APrice == null || movieForDetailedDto.AMovieDescription ==null)
        {
            return BadRequest("movie details not valid");
        }

        var movieToCreate = _mapper.Map<TblMovie>(movieForDetailedDto);

        var createdMovie = await _repo.AddMovie(movieToCreate);

        return Ok(createdMovie);
    }

Test that worked for the above controller-

        [Test]
    public async Task GivenAValidMovie_WhenIPostANewMovie_ThenItReturnsOkWithResponse()
    {
        _mockMovieRepository = new Mock<IMovieRepository>();
        _mockMovieMapper = new Mock<IMapper>();
        TblMovie expectedMovie = new TblMovie
        {
            AMovieId = 55,
            ATitle = "redemption",
            AMovieDescription = "An action comedy adventure about brilliant robotics prodigy Hiro Hamada, who finds himself in the grips of a criminal plot that threatens to destroy the fast-paced, high-tech city of San Fransokyo. With the help of his closest companion-a robot named Baymax-Hiro joins forces with a reluctant team of first-time crime fighters on a mission to save their city.",
            ADuration = "105 min",
            APrice = "10",
            APurchasePrice = "25",
            ARating = 5,
            AImageLink = "http://upload.wikimedia.org/wikipedia/en/4/4b/Big_Hero_6_%28film%29_poster.jpg",
            ATrailerLink = "//www.youtube.com/embed/z3biFxZIJOQ",
            AGenre = "Comedy",
            AWideImage = "https://github.com/tushar23091998/MovieRentalApp-FrontEnd/blob/master/src/app/images/bighero6.jpg?raw=true"
        };
        _mockMovieMapper.Setup(mapper => mapper.Map<TblMovie>(It.IsAny<MovieForDetailedDto>()))
            .Returns(expectedMovie);
        _mockMovieRepository.Setup(repo => repo.AddMovie(It.IsAny<TblMovie>()))
            .ReturnsAsync((TblMovie movie) => movie);

        _moviesController = new MoviesController(_mockMovieRepository.Object, _mockMovieMapper.Object);
        var tblMovie = await _moviesController.AddMovie(new MovieForDetailedDto
        {
            AMovieId = 55,
            ATitle = "redemption",
            AMovieDescription = "An action comedy adventure about brilliant robotics prodigy Hiro Hamada, who finds himself in the grips of a criminal plot that threatens to destroy the fast-paced, high-tech city of San Fransokyo. With the help of his closest companion-a robot named Baymax-Hiro joins forces with a reluctant team of first-time crime fighters on a mission to save their city.",
            ADuration = "105 min",
            APrice = "10",
            APurchasePrice = "25",
            ARating = 5,
            AImageLink = "http://upload.wikimedia.org/wikipedia/en/4/4b/Big_Hero_6_%28film%29_poster.jpg",
            ATrailerLink = "//www.youtube.com/embed/z3biFxZIJOQ",
            AGenre = "Comedy",
            AWideImage = "https://github.com/tushar23091998/MovieRentalApp-FrontEnd/blob/master/src/app/images/bighero6.jpg?raw=true"
        });
        var okResult = tblMovie as OkObjectResult;
        Assert.AreEqual(200, okResult.StatusCode);
        Assert.NotNull(okResult);
        Assert.IsAssignableFrom<OkObjectResult>(tblMovie);
        var result = ((OkObjectResult)tblMovie).Value;
        Assert.NotNull(result);
        Assert.AreEqual(expectedMovie,result);
        Assert.IsAssignableFrom<TblMovie>(result);


    }

So my previous repo functions which took only one parameter were working fine.

Thanks for your time. Apologies if the answer is too obvious.


Solution

  • .ReturnsAsync((TblUser user) =>
    

    has only one parameter. This should be

    .ReturnsAsync((TblUser user, string password) =>