EF Core in-memory database generate System.InvalidOperationException when testing an update operation - c#

I got the following error when I try to test an update operation using Entity Framework core:
System.InvalidOperationException : The instance of entity type 'Companies' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
After doing some research, I tried everything that I found:
Create in scope DB context
deattach and attached the object I want to update from the DB context
Return the object to be updated using "AsNoTracking()" , my repository actually do this.
For the testing I am using EF in-memmory database with it fixture, I am using XUnit and .NET 5.
Can I get any help with this please?
Here is my code:
// The repository I am trying to test
public class RepositoryBase<T> : ICrudRepository<T> where T : class, IModel
{
protected PrjDbContext DatabaseContext { get; set; }
public RepositoryBase(PrjDbContext databaseContext) => DatabaseContext = databaseContext;
protected IQueryable<T> FindAll() => DatabaseContext.Set<T>().AsNoTracking();
protected IQueryable<T> FindBy(Expression<Func<T, bool>> expression) => DatabaseContext.Set<T>().Where(expression).AsNoTracking();
public void Create(T entity) => DatabaseContext.Set<T>().Add(entity);
public void Update(T entity) => DatabaseContext.Set<T>().Update(entity);
public void Delete(T entity) => DatabaseContext.Set<T>().Remove(entity);
public async Task<IEnumerable<T>> ReadAllAsync() => await FindAll().ToListAsync().ConfigureAwait(false);
public async Task<T> ReadByIdAsync(int id) => await FindBy(entity => entity.Id.Equals(id)).FirstOrDefaultAsync().ConfigureAwait(false);
}
//The Database context
public partial class PrjDbContext : DbContext
{
public PrjDbContext()
{
}
public PrjDbContext(DbContextOptions<PrjDbContext> options)
: base(options)
{
}
public virtual DbSet<Companies> Companies { get; set; }
}
// This is my fixture with the in-memory Database
public sealed class PrjSeedDataFixture : IDisposable
{
public PrjDbContext DbContext { get; }
public PrjSeedDataFixture(string name)
{
string databaseName = "PrjDatabase_" + name + "_" + DateTime.Now.ToFileTimeUtc();
DbContextOptions<PrjDbContext> options = new DbContextOptionsBuilder<PrjDbContext>()
.UseInMemoryDatabase(databaseName)
.EnableSensitiveDataLogging()
.Options;
DbContext = new PrjDbContext(options);
// Load Companies
DbContext.Companies.Add(new Companies { Id = 1, Name = "Customer 1", Status = 0, Created = DateTime.Now, LogoName = "FakeLogo.jpg", LogoPath = "/LogoPath/SecondFolder/", ModifiedBy = "Admin" });
DbContext.Companies.AsNoTracking();
DbContext.SaveChanges();
}
public void Dispose()
{
DbContext.Dispose();
}
}
The test method "Update_WhenCalled_UpdateACompanyObject", is not working for me.
// And finally, this is my test class, Create_WhenCalled_CreatesNewCompanyObject pass the test, but Update_WhenCalled_UpdateACompanyObject isn't passing the test.
public class RepositoryBaseCompanyTests
{
private Companies _newCompany;
private PrjDbContext _databaseContext;
private RepositoryBase<Companies> _sut;
public RepositoryBaseCompanyTests()
{
_newCompany = new Companies {Id = 2};
_databaseContext = new PrjSeedDataFixture("RepositoryBase").DbContext;
_sut = new RepositoryBase<Companies>(_databaseContext);
}
[Fact]
public void Create_WhenCalled_CreatesNewCompanyObject()
{
//Act
_sut.Create(_newCompany);
_databaseContext.SaveChanges();
//Assert
Assert.Equal(2, _databaseContext.Companies.Where( x => x.Id == 2).FirstOrDefault().Id);
}
[Fact]
public async void Update_WhenCalled_UpdateACompanyObject()
{
//Arrange
var company = await _sut.ReadByIdAsync(1);
company.Name = "Customer 2";
//_databaseContext.Entry(company).State = EntityState.Detached;
//_databaseContext.Attach(company);
//_databaseContext.Entry(company).State = EntityState.Modified;
//Act
_sut.Update(company);
await _databaseContext.SaveChangesAsync();
//Assert
Assert.Equal("Customer 2", _databaseContext.Companies.Where(x => x.Id == 1).FirstOrDefault().Name);
}
}

If you are using EF Core 5.0 then call DbContext.ChangeTracker.Clear() (or go through DbContext.Entries collection and set state to Detached for earlier ones) after DbContext.SaveChanges(); in PrjSeedDataFixture ctor. Adding/Updating an entry makes it tracked and you are reusing the context that created an entry with Id = 1, so when _sut.Update(company); is called it will try to track it again (since ReadByIdAsync should return an untracked one).
P.S.
Adding an extra repository abstraction layer around EF can be considered as antipattern (because EF already implements repository/UoW patterns) and the issue you are having can be one of the examples of why that is true and why this abstraction can be a leaky one. So if you still decide that having one is a good idea - you need to proceed with caution.

Related

Moq mocking EF DbContext

I have a Repository pattern that interacts with Entity Framework.
I'd like to run some unit tests on the repository, and for this reason, I would like to mock DbContext.
So I've created a unit test project (.Net Core 3.1), using Moq as package for unit testing, everything seems to be ok, but when I perform a .ToListAsync() on my repository it throws the following exception:
System.NotImplementedException : The method or operation is not
implemented. Stack Trace: 
IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)
ConfiguredCancelableAsyncEnumerable1.GetAsyncEnumerator() EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1
source, CancellationToken cancellationToken)
The source code:
public class Customer
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class CustomersDbContext : DbContext
{
public virtual DbSet<Customer> Customers { get; set; }
public CustomersDbContext(DbContextOptions<Customer> options) : base(options) { }
}
public interface ICustomerRepository
{
Task<IEnumerable<Customer>> GetCustomersAsync(Guid? customerId);
}
public class CustomerRepository : ICustomerRepository
{
private readonly CustomersDbContext _dbContext;
public CustomerRepository(CustomersDbContext dbContext)
{
_dbContext = dbContext;
_dbContext.Database.EnsureCreated();
}
public async Task<IEnumerable<Customer>> GetCustomersAsync(Guid? customerId)
{
IEnumerable<Customer> customers = null;
if (customerId.HasValue)
{
var customer = await _dbContext.Customers.FindAsync(new object[] { customerId.Value }, CancellationToken.None);
if (customer != null)
customers = new List<Customer>() { customer };
}
else
{
customers = await _dbContext.Customers.ToListAsync(CancellationToken.None);
}
return customers;
}
}
public class CustomerServiceUnitTests
{
private Mock<CustomersDbContext> GetCustomerDbContextMock()
{
var data = new List<Customer>()
{
new Customer()
{
Id = Guid.NewGuid(),
Name = "Name 1"
},
new Customer()
{
Id = Guid.NewGuid(),
Name = "Name 2"
}
}.AsQueryable();
var mockSet = new Mock<DbSet<Customer>>();
mockSet.As<IQueryable<Customer>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Customer>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Customer>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Customer>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
var optionsBuilder = new DbContextOptions<CustomersDbContext>();
var mockContext = new Mock<CustomersDbContext>(optionsBuilder);
Mock<DatabaseFacade> databaseFacade = new Mock<DatabaseFacade>(mockContext.Object);
databaseFacade.Setup(d => d.EnsureCreatedAsync(CancellationToken.None)).Returns(Task.FromResult(true));
mockContext.Setup(c => c.Database).Returns(databaseFacade.Object);
mockContext.Setup(c => c.Customers).Returns(mockSet.Object);
return mockContext;
}
[Fact]
public async Task Infrastructure_CustomerRepository_GetAll()
{
var mockContext = this.GetCustomerDbContextMock();
ICustomerRepository customerRepository = new CustomerRepository(mockContext.Object);
var customers = await customerRepository.GetCustomersAsync(null);
Assert.NotNull(customers);
Assert.Equal(2, customers.Count());
}
}
If I send an ID filled to the repository it works fine, so this seems to be not ok only for .ToListAsync().
I'm kinda stuck here, what can I do to overcome this?
You cannot mock DbSet query functionality. This is explained in the docs:
Properly mocking DbSet query functionality is not possible, since queries are expressed via LINQ operators, which are static
extension method calls over IQueryable. As a result, when some
people talk about "mocking DbSet", what they really mean is that they
create a DbSet backed by an in-memory collection, and then evaluate
query operators against that collection in memory, just like a simple
IEnumerable. Rather than a mock, this is actually a sort of fake,
where the in-memory collection replaces the the real database.
In order to execute Asynchronous read operation (ToListAsync()) you need to mock an additional interface called "IDBAsyncQueryProvider".
Here's is the required link you can follow. It is under the heading "Testing with async queries"

EF Core In-Memory Database Not Saving To DbSet

Overview
I am currently unit/integration testing my repository pattern with the in-memory database EF Core provides. I am on version 6.0.1 for the Microsoft.EntityFrameworkCore.InMemory nuget and using Visual Studio 2022. I am running into issues saving data to the database. Below I have included snippets of my code.
My data model:
public class Example : IExample
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
My base repository class:
public class Repository<T> : IRepository<T> where T : class
private readonly DbSet<T> _dbSet;
private readonly DbContext _db;
public Repository(DbContext db)
{
_dbSet = db.Set<T>();
_db = db;
}
public virtual async Task Add(T entity)
{
await _dbSet.AddAsync(entity);
}
public virtual async Task<int> SaveAsync(CancellationToken token = default)
{
return await _db.SaveChangesAsync(token);
}
My repository class:
public class ExampleRepo : Repository<Example>
public ExampleRepo(ExampleContext db) : base(db)
{
}
My DbContext Class
I can show my IEntityConfiguration class for the ExampleBuilder shown below if it is needed but I don't believe that to be the problem.
public class ExampleContext : DbContext
private readonly IHttpContextAccessor? _httpContextAccessor;
public ExampleContext(DbContextOptions<ExampleContext> options) : base(options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
}
public ExampleContext(DbContextOptions<ExampleContext> options, IHttpContextAccessor httpContextAccessor)
: base(options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
public DbSet<Example>? Examples { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
var assembly = Assembly.GetAssembly(typeof(ExampleBuilder));
if (assembly is null)
{
throw new DataException("Could not find the assembly containing the designated model builder");
}
builder.ApplyConfigurationsFromAssembly(assembly);
foreach (var entity in builder.Model.GetEntityTypes())
{
entity.SetSchema("dbo");
}
}
public override Task<int> SaveChangesAsync(CancellationToken token = default)
{
foreach (var entry in ChangeTracker.Entries()
.Where(x => x.State is EntityState.Added or EntityState.Modified or EntityState.Deleted))
{
var user = _httpContextAccessor?.HttpContext?.User?.Identity?.Name ?? "User";
entry.Property("ModifiedBy").CurrentValue = user;
if (entry.State == EntityState.Added)
{
entry.Property("CreatedBy").CurrentValue = user;
}
if (entry.State != EntityState.Deleted) continue;
entry.State = EntityState.Modified;
entry.Property("IsActive").CurrentValue = false;
}
return base.SaveChangesAsync(token);
}
My DbContext factory method:
private ExampleContext GenerateDbContext()
{
var options = new DbContextOptionsBuilder<ExampleContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ExampleContext(options);
}
My unit/integration test utilizing xUnit and NET6.0
[Fact]
public async Task GetAllEntities_ShouldReturnEntities_WhenEntitiesExist()
{
// Arrange
// I used Bogus nuget for single source for generating valid models
var entity = ExampleFaker.GetModelFaker()
.Generate();
await using var context = GenerateDbContext();
var repo = new ExampleRepo(context);
await repo.Add(entity);
var changes = await repo.SaveAsync();
// Act
// Consulted this link already. Bottom answer is most related
//https://stackoverflow.com/questions/46184937/dbcontext-not-returning-local-objects
var response = await repo.GetAll();
// Assess
TestOutputHelper.WriteLine($"Added entity to repository: {entity.ToJson()}");
TestOutputHelper.WriteLine("Expected entities saved: 1");
TestOutputHelper.WriteLine($"Actual entities saved: {changes}");
TestOutputHelper.WriteLine($"Response: {response?.ToJson()}");
// Assert
Assert.Equal(1, changes);
Assert.NotNull(response);
Assert.NotEmpty(response);
Assert.IsType<List<Example>>(response.ToList());
}
Analysis & Issue
The changes variable returns 1 so I interpret this as EF does not have any issue with my model as well as I would think it successfully saved my model in the in-memory database. However, during my GetAll retrieval, no data is returned. When I debug and look into the repository private members, it shows the DbSet is empty so it is not the GetAll method causing the issue either. Since this is also just within the scope of the unit test, I don't think my Program.cs configuration has anything to do with the issue I am seeing. I have been looking at this for quite a while and can't figure out the small detail I am probably missing for the life of me.
Thank you for your help in advance.

Entity Framework mocking requires global context

I have recently began to dig into Entity Framework unit-testing with Entity Framework 6 mocking.
I have noticed the following thing:
Entity Framework mocking forces me to create a global context in my BL class, for example:
public class RefundRepayment : IDisposable
{
protected DbContext _dbContext = new DbContext();
/* more properties and class code */
public void Dispose()
{
_dbContext.Dispose();
}
}
I can't quite figure it out, as I'd rather implement the using statement in every method in order to deal with the DbContext, my code will look like:
public class RefundRepayment
{
/* more properties and class code */
public void AccessDb()
{
using(DbContext dbContext = new DbContext())
{
/* db code here */
}
}
}
Is there any specific reason why should we initialize a global context instead of implementing the using statement?
First off, you need to be using DI (via ninject, Unity, Core, etc) to pull this off.
Let me show you a simple sample of an EF GetAll() testing my MVC controller.
[Fact]
public void GetAllOk()
{
// Arrange
// Act
var result = _controller.GetAll() as OkObjectResult;
// Assert
Assert.NotNull(result);
var recordList = result.Value as List<DTO.Account>;
Assert.NotNull(recordList);
Assert.Equal(4, recordList.Count);
}
It relies on this startup code...
public class AccountsControllerTests
{
DatabaseFixture _fixture;
AccountsControllerV1 _controller;
public AccountsControllerTests(DatabaseFixture fixture)
{
_fixture = fixture;
_controller = new AccountsControllerV1(_fixture._uow);
}
What is DatabaseFixture? Glad you asked...
public class DatabaseFixture : IDisposable
{
public ApplicationDbContext _context;
public DbContextOptions<ApplicationDbContext> _options;
public IUoW _uow;
public DatabaseFixture()
{
var x = Directory.GetCurrentDirectory();
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.Tests.json", optional : true)
.Build();
_options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ProviderTests")
.Options;
_context = new ApplicationDbContext(_options);
_context.Database.EnsureCreated();
Initialize();
_uow = new UoW(_context);
}
private void Initialize()
{
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 1", AccountID = "", AccountUniqueID = "" });
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 2", AccountID = "", AccountUniqueID = "" });
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 3", AccountID = "", AccountUniqueID = "" });
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 4", AccountID = "", AccountUniqueID = "" });
_context.SaveChanges();
}
public void Dispose()
{
// Clean Up
_context.Database.EnsureDeleted();
}
}
[CollectionDefinition("Database Collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
}
A few definitions used in the above code. I used a Unit of Work Pattern that contains references to all my EF repositories. I kept Entity (Database) classes and DTO (Data Transfer Object) Classes separate. I used an in-memory replacement for the EF database that I initialize at the beginning of each run and/or test so that my data is always known. I inject the Database Fixture into my test class (not each test) so I am not creating/destroying constantly. Then I create my controller passing in my database UoW definition.
You're real controller requires injection of the UoW container you've created with the real database. You are merely substituting a controlled database environment for your test.
public AccountsControllerV1(IUoW uow)
{
_uow = uow;
}
And yes, I use versioning for the sharp-eyed. And yes, this is a Core 2 example. Still applicable for EF 6, just need 3rd party DI ;)
And the controller method I am testing?
[HttpGet("accounts", Name ="GetAccounts")]
public IActionResult GetAll()
{
try
{
var recordList = _uow.Accounts.GetAll();
List<DTO.Account> results = new List<DTO.Account>();
if (recordList != null)
{
results = recordList.Select(r => Map(r)).ToList();
}
log.Info($"Providers: GetAccounts: Success: {results.Count} records returned");
return Ok(results);
}
catch (Exception ex)
{
log.Error($"Providers: GetAccounts: Failed: {ex.Message}");
return BadRequest($"Providers: GetAccounts: Failed: {ex.Message}");
}
}

Mocking EF6 with validation that occurs on SaveChanges

I found a decent article to get me started on unit testing my Entity Framework-based application using Moq: https://msdn.microsoft.com/en-us/data/dn314429.aspx
This issue I'm having is that the SaveChanges method of the Mock does not appear to trigger the ValidateEntity method like it normally would. None of the validation settings I configured in the EntityTypeConfiguration are being thrown as a DbEntityValidationException.
For example, my AddRoles_Fails_For_Empty_Name tests to make sure that the service cannot add a role with an empty name. Either the IsRequired() configuration is not being applied, or the ValidateEntity method is not being called. I should mention that it works correctly if I use the actual context in the web app.
I've included some of my relevant unit testing, DbContext, and Service code below.
Am I doing something incorrectly? Are there any known issues or workarounds?
Role DB Map
public class RoleMap : EntityTypeConfiguration<Role>
{
public RoleMap()
{
ToTable("bm_Roles");
HasKey(r => r.Id);
Property(r => r.Name).IsRequired().HasMaxLength(100).HasIndex(new IndexAttribute("UX_Role_Name") { IsUnique = true });
Property(r => r.Description).HasMaxLength(500);
}
}
DbContext
public class BlueMoonContext : DbContext, IBlueMoonContext
{
public BlueMoonContext() : base("name=BlueMoon")
{
}
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.AddFromAssembly(typeof(BlueMoonContext).Assembly);
}
public void MarkAsModified<T>(T entity) where T : class
{
entity.ThrowIfNull("entity");
Entry<T>(entity).State = EntityState.Modified;
}
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
if (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified)
{
// Perform validations that require database lookups
if (entityEntry.Entity is Role)
{
ValidateRole((Role)entityEntry.Entity, result);
}
else if (entityEntry.Entity is User)
{
ValidateUser((User)entityEntry.Entity, result);
}
}
return result;
}
private void ValidateRole(Role role, DbEntityValidationResult result)
{
if (role.Name.HasValue() && !Roles.NameAvailable(role.Name, role.Id))
{
result.ValidationErrors.Add(new DbValidationError("Name", "Already in use"));
}
}
private void ValidateUser(User user, DbEntityValidationResult result)
{
if (user.UserName.HasValue() && !Users.UserNameAvailable(user.UserName, user.Id))
{
result.ValidationErrors.Add(new DbValidationError("UserName", "Already in use"));
}
if (user.Email.HasValue() && !Users.UserNameAvailable(user.UserName, user.Id))
{
result.ValidationErrors.Add(new DbValidationError("Email", "Already in use"));
}
}
}
Account Service
public class AccountService : BaseService, IAccountService
{
private IPasswordHasher _passwordHasher;
public AccountService(IBlueMoonContext context, IPasswordHasher passwordHasher) : base(context)
{
_passwordHasher = passwordHasher;
}
public ServiceResult CreateRole(Role role)
{
role.ThrowIfNull("role");
Context.Roles.Add(role);
return Save();
}
// Copied from base service class
protected ServiceResult Save()
{
var result = new ServiceResult();
try
{
Context.SaveChanges();
}
catch (DbEntityValidationException validationException)
{
foreach (var validationError in validationException.EntityValidationErrors)
{
foreach (var error in validationError.ValidationErrors)
{
result.AddError(error.ErrorMessage, error.PropertyName);
}
}
}
return result;
}
}
Unit Test
[TestFixture]
public class AccountServiceTests : BaseTest
{
protected Mock<MockBlueMoonContext> _context;
private IAccountService _accountService;
[TestFixtureSetUp]
public void Setup()
{
_context = new Mock<BlueMoonContext>();
var data = new List<Role>
{
new Role { Id = 1, Name = "Super Admin" },
new Role { Id = 2, Name = "Catalog Admin" },
new Role { Id = 3, Name = "Order Admin" }
}.AsQueryable();
var roleSet = CreateMockSet<Role>(data);
roleSet.Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>(ids => data.FirstOrDefault(d => d.Id == (int)ids[0]));
_context.Setup(m => m.Roles).Returns(roleSet.Object);
// _context.Setup(m => m.SaveChanges()).Returns(0);
_accountService = new AccountService(_context.Object, new CryptoPasswordHasher());
}
[Test]
public void AddRole_Fails_For_Empty_Name()
{
var role = new Role { Id = 4, Name = "" };
var result = _accountService.CreateRole(role);
Assert.False(result.Success);
}
}
SaveChanges is a virtual method which means you invoke a fake method....
You can create your mock CallBase = true, but it is not a good idea(it miss the idea of UT):
_context = new Mock<BlueMoonContext>(){ CallBase = true };
The above code will use the real implementation of BlueMoonContext for any method/property which is not explicitly setup.
RoleMap is responsible for your DB stracture, you should test it as a part of integration test(with DB).
In my opinion you should create an integration tests to verify the integrity(for example; cover RoleMap) with your DB, And create a UT using the Throw setup to cover the catch section(it's a part of your unit):
_contest.Setup(x => x.SaveChanges())
.Throws(new DbEntityValidationException());
Edit to answer the OP question in the comment
no, you don't have to separate the built in validation, you have to create another test(integration test). In this test you'll verify the validation behaviour: insert an illegal entity, expect that exception will raise(using ExpectedExceptionAttribute) and then verify that the DB is empty... to apply this behaviour use this pattern:
try
{
\\...
\\try to commit
}
catch(DbEntityValidationException ex)
{
\\do some validation, then:
throw;\\for ExpectedExceptionAttribute
}
I looked over the api of EntityTypeConfiguration, I didn't saw any contact which allows to UT the rules(unless you use tools like MsFakes, TypeMock Isolator there is no way to verify the ToTable/HasKey/Property was called). The class is being in used inside EntityFramework(which is a part of the BCL) in the integration test you don't have to verify that EntityFramework work properly, you are going to verify that your custom rules was integrated and works as you expect(In this answer you can read the reason not to test a BCL classes).
So use Moq in the UTs of AccountService. Create an integration tests for BlueMoonContext and RoleMap(without Moq).
By the way #LadislavMrnka offer an interesting way to test(integration test) EntityTypeConfiguration

How to mock Entity Framework 6 Async methods?

I am new in mocking. I want to mock up my base repository which is depend on Entity Framework 6 DbContext But I fail. I searched in Google a lot but did not get any sufficient result. At last I got an example at testing with async queries and try to follow but it is worked for me.
Here is my code :
DbContext :
public class TimeSketchContext : DbContext
{
public virtual DbSet<EmployeeSkill> EmployeeSkill { get; set; }
}
Base Repository :
public class BaseRepository<T> : IRepositoryBase<T> where T : class, IEntity, new()
{
protected readonly DbContext InnerDbContext;
protected DbSet<T> InnerDbSet;
public BaseRepository(DbContext innerDbContext)
{
InnerDbContext = innerDbContext;
InnerDbSet = InnerDbContext.Set<T>();
}
public virtual Task<T> FindAsync(long id)
{
return InnerDbSet.FirstOrDefaultAsync(x=>x.Id == id);
}
}
Test :
[Fact]
public async Task DbTest()
{
var dummyData = GetEmployeeSkills();
var mockSet = new Mock<DbSet<EmployeeSkill>>();
mockSet.As<IDbAsyncEnumerable<EmployeeSkill>>()
.Setup(x => x.GetAsyncEnumerator())
.Returns(new TestDbAsyncEnumerator<EmployeeSkill>(dummyData.GetEnumerator()));
mockSet.As<IQueryable<EmployeeSkill>>()
.Setup(x => x.Provider)
.Returns(new TestDbAsyncQueryProvider<EmployeeSkill>(dummyData.Provider));
mockSet.As<IQueryable<EmployeeSkill>>().Setup(m => m.Expression).Returns(dummyData.Expression);
mockSet.As<IQueryable<EmployeeSkill>>().Setup(m => m.ElementType).Returns(dummyData.ElementType);
mockSet.As<IQueryable<EmployeeSkill>>().Setup(m => m.GetEnumerator()).Returns(dummyData.GetEnumerator());
var mockContext = new Mock<TimeSketchContext>();
mockContext.Setup(c => c.EmployeeSkill).Returns(mockSet.Object);
var baseRepository = new BaseRepository<EmployeeSkill>(mockContext.Object);
var data = await baseRepository.FindAsync(1);
Assert.NotEqual(null, data);
}
private EmployeeSkill GetEmployeeSkill()
{
return new EmployeeSkill
{
SkillDescription = "SkillDescription",
SkillName = "SkillName",
Id = 1
};
}
private IQueryable<EmployeeSkill> GetEmployeeSkills()
{
return new List<EmployeeSkill>
{
GetEmployeeSkill(),
GetEmployeeSkill(),
GetEmployeeSkill(),
}.AsQueryable();
}
Result is :
Assert.NotEqual() Failure
I think problem is
public BaseRepository(DbContext innerDbContext)
{
InnerDbContext = innerDbContext;
InnerDbSet = InnerDbContext.Set<T>(); <<<<<<<<<<<
}
But don`t understand why and how to solve this.
I am using :
Visual Studio 2013 Ultimate
Moq
xUnit
Thank`s in advance.
You are right the problem is in your InnerDbContext.Set<T>(); statement.
In the current version of the EF (6.0.2) the DbContext.Set<T> method is not virtual so it cannot be mocked with Moq.
So you cannot easily make your test pass except by changing your design of the BaseRepository to not depend on the whole DbContext but on one DbSet<T>:
So something like:
public BaseRepository(DbSet<T> dbSet)
{
InnerDbSet = dbSet;
}
Then you can pass directly in your mocked DbSet.
Or you can create a wrapper interface for DbContext:
public interface IDbContext
{
DbSet<T> Set<T>() where T : class;
}
public class TimeSketchContext : DbContext, IDbContext
{
public virtual DbSet<EmployeeSkill> EmployeeSkill { get; set; }
}
Then use IDbContext in your BaseRepository:
public class BaseRepository<T> : IRepositoryBase<T> where T : class, IEntity, new()
{
protected readonly IDbContext InnerDbContext;
protected DbSet<T> InnerDbSet;
public BaseRepository(IDbContext innerDbContext)
{
InnerDbContext = innerDbContext;
InnerDbSet = InnerDbContext.Set<T>();
}
public virtual Task<T> FindAsync(long id)
{
return InnerDbSet.FirstOrDefaultAsync(x => x.Id == id);
}
}
And finally you just need to change two lines in your test to make it pass:
var mockContext = new Mock<IDbContext>();
mockContext.Setup(c => c.Set<EmployeeSkill>()).Returns(mockSet.Object);

Categories