I am mocking DbContext as well as DbSet. My problem is while retrieve data using FirstOrDefault
and change the value of some property, It automatically update in DbSet before doing dbContext.Update()
.
Here is my implementations,
public Mock<TContext> GetMock<TData, TContext>(List<TData> data,
Expression<Func<TContext, DbSet<TData>>> dbSetSelectionExpression) where TData : class where TContext : DbContext
{
var queryableData = data.AsQueryable();
var stubSet = new Mock<DbSet<TData>>();
var stubContext = new Mock<TContext>();
stubSet.As<IQueryable<TData>>().Setup(s => s.Provider).Returns(queryableData.Provider);
stubSet.As<IQueryable<TData>>().Setup(s => s.Expression).Returns(queryableData.Expression);
stubSet.As<IQueryable<TData>>().Setup(s => s.ElementType).Returns(queryableData.ElementType);
stubSet.As<IQueryable<TData>>().Setup(s => s.GetEnumerator()).Returns(() => queryableData.GetEnumerator());
stubContext.Setup(dbSetSelectionExpression).Returns(stubSet.Object);
return stubContext;
}
var data = new List<MyEntity>
{
new MyEntity { Id = 1, Name = "Entity 1" },
new MyEntity { Id = 2, Name = "Entity 2" }
};
var dbContextMock=GetMock<ProductContext,MyEntity>(data,x=>x.MyEntity);
//retrieve Data
var data=dbContextMock.Object.MyEntity.FirstOrDefault(x=>x.Id==1);
//Change the value
data.Name="New Entity";
//After executing above line, it automatically update in dbContextMock.Object.MyEntity
How to Avoid the reference of Mock DbSet while retrieve data and change the data?
Don't mock DbContext. There's no reason and no benefit. What you try to do will end up testing your mocking code, not your controllers.
You can easily create a DbContext backed by the in-memory provider or SQLite in in-memory mode.
_contextOptions = new DbContextOptionsBuilder<BloggingContext>()
.UseInMemoryDatabase("BloggingControllerTest")
.ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.Options;
using var context = new BloggingContext(_contextOptions);
context.Database.EnsureCreated();
context.AddRange(
new Blog { Name = "Blog1", Url = "http://blog1.com" },
new Blog { Name = "Blog2", Url = "http://blog2.com" });
context.SaveChanges();
The in-memory provider doesn't support complex SQL, views etc. An alternative is to use SQLite in in-memory mode. The only change needed is to change the provider:
_connection = new SqliteConnection("Filename=:memory:");
_connection.Open();
_contextOptions = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(_connection)
.Options;
There's no need to call DbContext.Update
when you modify an object loaded using DbContext either. A DbContext is a Unit-of-Work and will detect all changes made to the objects it tracks. All you really need to load and modify an object is:
var blog=context.Find(1);
blog.Name="OtherName";
await context.SaveChanges();
SaveChanges
will detect and persist changes to all objects. Essentially, it commits all the changes you made since you created a DbContext. There's no reason to call it after every change. To roll back all pending changes just dispose the context
When you register a DbContext with AddDbContext
you specify the provider with .UseSqlServer
, UseSqlite
or UseInMemoryDatabase
. In a