Home > OS >  Mocking EF in Xunit
Mocking EF in Xunit

Time:04-06

I am new to unit testing and mocking but I am trying to add tests for existing CRUD functions. I know that I need to use MOQ library but I am not sure if I can do it with the function below. All CRUD functions have using statement inside them.

I am using .NET 5.0 and EF6

Example of the existing function:

public static async Task CreateRoleAsync(string roleName)
        {
            using(DbEntities db = new DbEntities())
            {
                Log newLog = CreateLog();
                Role newRole = new Role()
                {
                    Name = roleName,
                    Log = newLog
                };

                db.Roles.Add(newRole);
                await db.SaveChangesAsync();
            }
        }

Is it possible to maintain current structure of functions and implement mocking? Or should these functions be refactored to use dependency injections?

I would appreciate if you could point me in the right direction. Let me know if additional information is needed.

Thanks in advance.

CodePudding user response:

It seems to me that here you don't need to write unit test. What exactly will you assert? There is no business rule or functional requirement here. In such scenarios, integration test is adequate.

CodePudding user response:

I know that I need to use MOQ library

You not forced to use MOQ or any other mocking frameworks, you can achieve a lot without mocking frameworks, by creating fakes your self and not mocking the code which doesn't access external resources.

I am trying to add tests for existing CRUD functions

CRUD functions are mostly functions which update database state, for useful feedback I would suggest to write tests with actual database.

CodePudding user response:

Unit testing is facilitated by dependency injection, so code structure like what you have isn't very test-able beyond running integration-style tests which would configure the code to run against a known-state DbContext which could be a database with suitable test data or an in-memory data source.

Static methods also pose a problem with dependency injection.

A more test-able version of that method would be something like:

public class RoleService
{
    private readonly DbEntities _context = null;
    public RoleService(DbEntities context)
    {
        _context = context ?? throw new ArgumentNullException("context");
    }
  
    
    public async Task CreateRoleAsync(string roleName)
    {
        Log newLog = CreateLog();
        Role newRole = new Role()
        {
            Name = roleName,
            Log = newLog
        };

        _context.Roles.Add(newRole);
        await context.SaveChangesAsync();
    }
}

Now when you go to unit test RoleService.CreateRole you can mock the DbContext, though mocking one is arguably quite clumsy. Unit testing is one good reason to consider using a repository pattern along with a UnitOfWork pattern to manage the scope of the DbContext. These classes can expose much simpler interfaces which are easier to mock than DbContext and DbSet<TEntity>.

public class RoleService
{
    private readonly IDbContextScopeFactory _contextScopeFactory = null;
    private readonly IRoleRepository _roleRepository = null;
    public RoleService(IDbContextScopeFactory contextScopeFactory, IRoleRepository roleRepository)
    {
        _contextScopeFactory = contextScopeFactory ?? throw new ArgumentNullException("contextScopeFactory");
        _roleRepository = roleRepository ?? throw new ArgumentNullException("roleRepository");
    }
  
    
    public async Task CreateRoleAsync(string roleName)
    {
        using(var contextScope = _contextScopeFactory.Create())
        {
            Role newRole = _roleRepository.CreateRole(roleName);
            await contextScope.SaveChangesAsync();
        }
    }
}

The ContextScopeFactory comes from a unit of work pattern called Medhime DbContextScope to manage the scope of a DbContext and provide an ambient context locator for the Repository to find the scope it is under to resolve an instance of a DbContext.

The RoleRepository is used to wrap DbContext operations functions including creation of entities (Role and Log) to ensure that these are given enough information to create a valid entity and associated to the DbContext.

public class RoleRepository : IRoleRepository
{
    private readonly IAmbientDbContextLocator _contextLocator = null;
    public RoleRepository(IAmbientDbContextLocator contextLocator)
    {
        _contextLocator = contextLocator ?? throw new ArgumentNullException("contextLocator");
    }

    private DbEntities Context => _contextLocator.Get<DbEntities>();

    Role IRoleRepository.CreateRole(string roleName)
    {
        // Can do validation for duplicate roles, etc...
        var role = new Role
        {
            RoleName = roleName,
            Log = new Log();
        }
        Context.Roles.Add(role);
    }
}

This repository method probably looks quite similar to your Service code, but it is just serving as a factory method for our entity. Note that the Context is fetched from our ambient context scope locator, and while we are adding our new entity to the Context within that context scope, we aren't calling a SaveChanges(). That is the responsibility of the code managing the Context Scope. (Unit of Work). At the end of the day though, this Repository implementation is done so that all of this "work" against data can be mocked out when we want to test our Service. Where we might want to test this repository behaviour out, that would be best served by an integration test running against a database. Business logic is what unit tests would be concerned with and the idea would be that this business logic would be in our service, it just counts on the repository to handle data.

Now a unit test can mock out the IDbContextScopeFactory and the IRoleRepository dependencies for this service:

[Test]
public void EnsureCreateRoleCreatesANewRole()
{
    const string TestRoleName = "Boss";
    var mockContextScope = new Mock<IDbContextScope>();
    var mockContextScopeFactory = new Mock<IDbContextScopeFactory>();
    var mockRepository = new Mock<IRoleRepository>();
    var stubRole = new Role
    {
         RoleId = 1,
         RoleName = TestRoleName,
         Log = new Log();
    };

    mockContextScopeFactory.Setup(x => x.Create()).Returns(mockContextScope.Object);
    mockRepository.Setup(x => x.CreateRole(TestRoleName)).Returns(stubRole);

    var testService = new RoleService(mockContextScopeFactory.Object, mockRepository.Object);
    await testService.CreateRole(TestRoleName);

    mockRepository.Verify(x=> x.CreateRole(TestRoleName), Times.Once);
    mockContextScope.Verify(x => x.SaveChanges(), Times.Once);
}

Now this is a very bare-bones example as the service isn't really doing any business logic. Typically the code under test would do something more than simply attempt to create an entity without first performing some validation or such. This test does assert that the repository was given a request to create the role with the expected value, and that the Context Scope was told to save the changes. Whatever situational conditions that the code under test might encounter (duplicate names, handling a null/empty name, etc.) can be handled by test cases where you can assert values returned or verify that specific methods should, or should not be called. You can also assert whether exceptions are handled by having your mocks throw an exception when called rather than returning a value.

Testing with dependency injection (and inversion of control) opens the way for those dependencies to be substituted with mocks by the tests to take over responsibility for inner logic and state. Your original code structure scopes a DbContext inside the method and that DbContext is expecting a database so it cannot be "mocked out" since the code under test has no way to allow it to be substituted.

Hopefully this gives you some ideas about how to restructure code and write new classes and methods to be accommodating for unit testing.

  • Related