Search code examples
c#unit-testingmoqxunitc#-5.0

Unit Test Mock Database Returning Null


I'm new to unit testing and mocking databases. One of my tests SHOULD work, but when the module I'm testing accesses the InMemoryDatabase it returns null instead of the user object.

I'm using Moq and xUnit.

My Test Setup:

// Mock List of Db users
private List<User> GetFakeUsers()
{
    List<User> output = new List<User> {
        new User { Id = 1, Email = "[email protected]", Username = "admin_test", PasswordHash = System.Text.Encoding.UTF8.GetBytes("fgftfhstafgsrstf54"), Role = "admin" },
        new User { Id = 2, Email = "[email protected]", Username = "user2", PasswordHash = System.Text.Encoding.UTF8.GetBytes("fgftfhstafgsrstf54"), Role = "premium" },
        new User { Id = 3, Email = "[email protected]", Username = "user3", PasswordHash = System.Text.Encoding.UTF8.GetBytes("fgftfhstafgsrstf54"), Role = "basic" },
    };

    return output;
}


// Set up the InMemory Mock Database
private DataContext GetInMemoryMockDB()
{
    // Set Up Entity InMemory Database
    // I'm using the same DataContext as in the main program.
    var options = new DbContextOptionsBuilder<DataContext>()
        .UseInMemoryDatabase(databaseName: "UserServiceMockDb")
        .Options;

    // Set Up Mock AppSettings.json file
    // Our DataContext.cs has two parameters in its constructor a dbContext and an IConfiguration it uses to read the appsettings.json file.
    var config = new ConfigurationBuilder()
        .SetBasePath(@"...blahblahblah...")
        .AddJsonFile("mockAppSettings.json", optional: false, reloadOnChange: true)
        .AddEnvironmentVariables();

    _configuration = config.Build();

    var dbContext = new DataContext(options, _configuration);
    dbContext.Database.EnsureCreated();
    dbContext.Users.AddRange(GetFakeUsers());                        // Add our test users to the database

    return dbContext;
}

My Unit Test:


// Test - Check normal successful input. Get user3.
[Fact]
public async void GetUserByUsername_Works()
{
    using (var mock = AutoMock.GetLoose())
    {
        // Arrange
        // The module needs a few dependencies injected into its constructor...
        var mapperCfg = new MapperConfiguration(m => { m.AddProfile<API_Project.AutoMapperProfile>(); });
        IMapper mapper = mapperCfg.CreateMapper();
        var databaseContext = GetInMemoryMockDB();
        var loggerMock = new Mock<ILogger<UserService>>();
        var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
        
        // We have to supply an "admin" role claim
        mockHttpContextAccessor.Setup(x => x.HttpContext.User.FindFirst(It.IsAny<string>())).Returns(new Claim("role", "admin"));
        
        var suppliedUserList = GetFakeUsers();


        // Act
        var userService = new UserService(mapper, databaseContext, loggerMock.Object, mockHttpContextAccessor.Object);
        ServiceResponse<GetUserDto> returnedUser = await userService.GetUserByUsername("user3");                        // Lookup username "user3"        



        //Assert
        // Compare supplied user3 to the returned user.
        Assert.Equal(suppliedUserList[2].Email, returnedUser.Data.Email);
        Assert.Equal(suppliedUserList[2].Username, returnedUser.Data.Username);
        Assert.Equal(suppliedUserList[2].Role, returnedUser.Data.Role);

    }
}

And Finally, the unit I'm trying to test:

public async Task<ServiceResponse<GetUserDto>> GetUserByUsername(string username)
{
    var serviceResponse = new ServiceResponse<GetUserDto>();

    // Get user by USERNAME from the database
    // Check if the user role is "admin" ? [true] Lookup the specified user Id : [false] return only their user info
    // This is returning NULL for the username search?
    var dbUser =
        GetUserRole().Equals("admin") ?
        await _context.Users.FirstOrDefaultAsync(c => c.Username.Contains(username)) :
        await _context.Users.Where(u => u.Id == GetUserId()).FirstOrDefaultAsync();

    // FirstOrDefault() returns null if nothing is found.
    if (dbUser != null)
    {
        serviceResponse.Data = _mapper.Map<GetUserDto>(dbUser);
    }
    else
    {
        // Send a message to tell user of the error
        serviceResponse.Success = false;
        serviceResponse.Message = "Sorry, we couldn't find that user.";
        _logger.LogInformation("Error - Search for an unknown user. UserService.GetUserByUsername - USERNAME: {1}", username);
    }

    return serviceResponse;
}

The issue is "var dbUser" in the above module I'm testing is returning null. It works in my production database but not with the InMemoryDatabase. I'm guessing it can't handle the linq query or something?

This is my first time using InMemoryDatabases, so I don't really know their limits. I hope it's not due to a spelling mistake or something...


Solution

  • I don't see .SaveChanges() in the shown code, I suppose that debug will show uncommitted changes in your testing Db. So I would say that it's worth to add it after adding the users.