Home > Mobile >  EntityFrameworkCore writing unit tests - not able to pass mocked context
EntityFrameworkCore writing unit tests - not able to pass mocked context

Time:12-23

I'm having issues in writing a unit test for Get/Add methods on the Entities, not able to pass mocked context inside the DAO class for instantiation.

Its database first approach.

please find the required details below,

My DBContext looks like below

using Microsoft.EntityFrameworkCore;

public class EfDbContext : DbContext
{
    private readonly ISqlConnectionProvider _provider;

    public EfDbContext(ISqlConnectionProvider provider)
    {
        _provider = provider;
    }

    public EfDbContext(DbContextOptions<EfDbContext> options)
        : base(options)
    {}

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            string connectionString = _provider.ConnectionString;
            optionsBuilder.UseSqlServer(connectionString);
        }
    }

    public DbSet<ArbReservationEntity> ArbReservationEntities { get; set; }
}

my DAO class looks like below

public class SampleDAO : ISampleDao
{
    private readonly ILogger _logHandler;
    private readonly EfDbContext _dbContext;

    public SampleDAO(ISqlConnectionProvider provider, ILogger logHandler, EfDbContext dbContext)
    {
        _logHandler = logHandler;
            _sqlConnectionProvider = new TestSqlConnectionProvider("test", new AesEncryptDecrypt());
        _dbContext = dbContext;
    }

    public async Task AddAsync(SampleEntity entity)
    {
        _dbContext.SampleEntities.Add(entity);
        await _dbContext.SaveChangesAsync();
    }

    public async Task<SampleEntity> GetAsync(long uniqueId)
    {
        var entity = await _dbContext.SampleEntities.FirstOrDefaultAsync(a => a.Id == uniqueId);
        return entity;
    }
}

my unit tests looks like below:-

using Xunit;
using Moq;
using NSubstitute;

public class SampleDAOTests
{
    private ISampleDao _sampleDao;
    private readonly ILogger _logger;
    private readonly EfDbContext _dbContext;
    private readonly ISqlConnectionProvider _sqlConnectionProvider;

    public SampleDAOTests()
    {
        _logger = Substitute.For<ILogger>();
        _sqlConnectionProvider = new EncryptedSqlConnectionProvider("test", new AesEncryptDecrypt());
        _sampleDao = new SampleDAO(_sqlConnectionProvider, _logger, _dbContext);

    }

    [Fact]
    public async Task GetAsync_Should_Return_SampleEntity()
    {
        var sampleEntity = new SampleEntity() { };

        var sampleEntityList = new List<SampleEntity>()
        {
            new SampleEntity() { Id = 1, CartId = "1" },
            new SampleEntity() { Id = 2, CartId = "2" }
        };

        var mockSet = new Mock<DbSet<SampleEntity>>();
        mockSet.As<IQueryable<SampleEntity>>().Setup(m => m.GetEnumerator()).Returns(() => sampleEntityList.GetEnumerator());

        var mockContext = new Mock<EfDbContext>();

        _sampleDao = new SampleDAO(_sqlConnectionProvider, _logger, mockContext.Object);

        var entities = await _sampleDao.GetAsync(123);

        //Assert
        Assert.NotNull(entities);
    }
}

The below exception is thrown when I try to inject the mockContext inside the sampleDAO class instantiation

_sampleDao = new SampleDAO(_sqlConnectionProvider, _logger, mockContext.Object);

Exception

Message: 
    System.ArgumentException : Can not instantiate proxy of class: Repository.EF.EfDbContext.
    Could not find a parameterless constructor. (Parameter 'constructorArguments')
    ---- System.MissingMethodException : Constructor on type 'Castle.Proxies.EfDbContextProxy' not found.
    
Stack Trace: 
    ProxyGenerator.CreateClassProxyInstance(Type proxyType, List`1 proxyArguments, Type classToProxy, Object[] constructorArguments)
    ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors)
    CastleProxyFactory.CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, Object[] arguments) line 62
    Mock`1.InitializeInstance() line 309
    Mock`1.OnGetObject() line 323
    Mock.get_Object() line 179
    Mock`1.get_Object() line 281
    SampleDAOTests.GetAsync_Should_Return_SampleEntity() line 73
    --- End of stack trace from previous location ---
    ----- Inner Stack Trace -----
    RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
    Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
    ProxyGenerator.CreateClassProxyInstance(Type proxyType, List`1 proxyArguments, Type classToProxy, Object[] constructorArguments)

CodePudding user response:

It's not the best practice to use Moq to wrap datasets. Instead of that it will be better to use either InMemory db provider or Sqlite provider for tests.

First one will cover most of the basic scenarios. Second one will help you in case if you using transactions or other features which are not supported by InMemory.

You can initialize context in test startup, fill it with necessary data and check it's contents after the tested logic will complete execution.

  • Related