I want to unit-test the retrieval methods from a repository which has a mocked DbContext, but I am not able to set the mocked DbSet values to the repository.
The Repository looks like this:
public class ChangeLogRepository : Repository<ChangeLog>, IChangeLogRepository
{
public ChangeLogRepository(IDbContext context, long tenantId) : base(context, tenantId)
{
}
}
The base class:
public class Repository<TEntity> where TEntity : class {
protected readonly IDbContext Context;
protected DbSet<TEntity> Entities { get; set; }
public long TenantId { get; set; }
protected Repository(IDbContext context, long tenant)
{
Context = context;
TenantId = tenant;
Entities = Context.Set<TEntity>();
}
public List<TEntity> GetAll()
{
return Entities.ToList();
}
//..
}
Last but not least, the test class:
[TestClass]
public class ChangeLogRepository_Test
{
private ChangeLogRepository repository;
private List<ChangeLog> allTestData;
[TestInitialize]
public void TestInitialize()
{
var dbContext = new Mock<IDbContext>();
allTestData = new List<ChangeLog>() {
new ChangeLog { Id = 10, EntityName = "User",PropertyName = "UserName",PrimaryKeyValue = 1,OldValue = "Max",NewValue = "Moritz",DateChanged = DateTime.Now,FieldType = ChangeLogFieldType.Default },
new ChangeLog { Id = 10, EntityName = "User",PropertyName = "CreatedAt",PrimaryKeyValue =2,OldValue = "15/06/2017",NewValue = "15/06/2017",DateChanged = DateTime.Now,FieldType = ChangeLogFieldType.Date },
new ChangeLog { Id = 10, EntityName = "Role",PropertyName = "RoleName",PrimaryKeyValue = 56,OldValue = "Admin",NewValue = "Administrator",DateChanged = DateTime.Now,FieldType = ChangeLogFieldType.Default },
};
var changelogs = MockDbSet(allTestData);
dbContext.Setup(m => m.Set<ChangeLog>()).Returns(() => changelogs);
repository = new ChangeLogRepository(dbContext.Object, 10);
}
[TestMethod]
public void Setup_Test()
{
Assert.AreEqual(repository.GetAll(), allTestData);
}
private static DbSet<T> MockDbSet<T>(IEnumerable<T> list) where T : class, new()
{
IQueryable<T> queryableList = list.AsQueryable();
Mock<DbSet<T>> dbSetMock = new Mock<DbSet<T>>();
dbSetMock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(queryableList.Provider);
dbSetMock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(queryableList.Expression);
dbSetMock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(queryableList.ElementType);
dbSetMock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(queryableList.GetEnumerator());
return dbSetMock.Object;
}
}
If I run it, the test fails, because the getAll() method retuns null. It seams, that the property 'Entities' wasn't correctly initialized by the mocked Set() method.
When I set a breakpoint in the repository-constructor
and examine the Entities property, under 'Expression > Value > Result View' the three Entries appear. Under the first Result View there are one "Enumeration yielded no results" message and two rows with ? in it (Visual Studio 2017).
How can I mock the entities in the repository correctly? What am I doing wrong?
I recreated the test based exactly on the example provided in the original question and was unable to reproduce the null issue. The mock returned a populated collection just as it was configured to do.
A problem occurred however when comparing the two collections,
Assert.AreEqual(repository.GetAll(), allTestData);
they were not considered equal. Expected, as the ToList would be creating a new list which would obviously be a different reference to the original list used as the data source for the mock.
Compare the two collections using CollectionAssert.AreEquivalent instead
[TestMethod]
public void Setup_Test() {
var actual = repository.GetAll();
CollectionAssert.AreEquivalent(allTestData, actual);
}
and the test passes.
The EF documentation covers this.
Use IDbSet<T> in your context.
Create a fake implementation of IDbSet<T> for your tests.
See https://msdn.microsoft.com/en-gb/data/dn314431.
Note there is also a page on using mocking frameworks but I've never done that: https://msdn.microsoft.com/en-gb/data/dn314429.
Note also, if using EF Core (aka EF7) there is an in memory provider avoiding needing doubles at all.
Related
How do you mock AsNoTracking or is there a better workaround for this Problem?
Example:
public class MyContext : MyContextBase
{
// Constructor
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
// Public properties
public DbSet<MyList> MyLists{ get; set; }
}
public class MyList
{
public string Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Blocked { get; set; }
}
public class MyController : MyControllerBase
{
private MyContext ContactContext = this.ServiceProvider.GetService<MyContext>();
public MyController(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
private bool isContact(string firstName, string lastName)
{
try
{
var list = this
.ContactContext
.MyLists
.AsNoTracking() // !!!Here it explodes!!!
.FirstOrDefault(entity => entity.FirstName == firstName && entity.LastName == lastName);
return list != null;
}
catch (Exception exception)
{
throws Exception;
}
return false;
}
}
My test:
using Moq;
using Xunit;
[Fact]
[Trait("Category", "Controller")]
public void Test()
{
string firstName = "Bob";
string lastName = "Baumeister";
// Creating a list with the expectad data
var fakeContacts = new MyList[]
{
new MyList() { FirstName = "Ted", LastName = "Teddy" },
new MyList() { PartnerId = "Bob", Email = "Baumeister" }
};
// Mocking the DbSet<MyList>
var dbSet = CreateMockSet(fakeContacts.AsQueryable());
// Setting the mocked dbSet in ContactContext
ContactContext contactContext = new ContactContext(new DbContextOptions<ContactContext>())
{
MyLists = dbSet.Object
};
// Mocking ServiceProvider
serviceProvider
.Setup(s => s.GetService(typeof(ContactContext)))
.Returns(contactContext);
// Creating a controller
var controller = new ContactController(serviceProvider.Object);
// Act
bool result = controller.isContact(firstName, lastName)
// Assert
Assert.True(result);
}
private Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> data)
where T : class
{
var queryableData = data.AsQueryable();
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IQueryable<T>>().Setup(m => m.Provider)
.Returns(queryableData.Provider);
mockSet.As<IQueryable<T>>().Setup(m => m.Expression)
.Returns(queryableData.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType)
.Returns(queryableData.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
.Returns(queryableData.GetEnumerator());
return mockSet;
}
Every time I run this Test, the Exception that is thrown in isContact(String firstName, String lastName) at AsNoTracking() is:
Exception.Message:
There is no method 'AsNoTracking' on type
'Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions'
that matches the specified arguments
Exception.StackTrace:
at System.Linq.EnumerableRewriter.FindMethod(Type type, String name, ReadOnlyCollection'1 args, Type[] typeArgs)
at System.Linq.EnumerableRewriter.VisitMethodCall(MethodCallExpression m)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at System.Linq.EnumerableQuery'1.GetEnumerator()
at System.Linq.EnumerableQuery'1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at My.Package.Contact.Controller.MyController.isContact(String firstName, String lastName) in C:\Users\source\repos\src\My.Package\My.Package.Contact\Controller\MyController.cs:line 31
My attempts:
Trying to mock AsNoTracking like suggested in stackoverflow: mock-asnotracking-entity-framework:
mockSet.As<IQueryable<T>>().Setup(m => m.AsNoTracking<T>())
.Returns(mockSet.Object);
results in ASP.NET Core in a System.NotSupportedException:
'Invalid setup on an extension method: m => m.AsNoTracking()'
mockSet.Setup(m => m.AsNoTracking())
.Returns(mockSet.Object);
After taking a better look at Microsoft.EntityFrameworkCore EntityFrameworkQueryableExtensions EntityFrameworkCore EntityFrameworkQueryableExtensions.cs at AtNoTracking():
public static IQueryable<TEntity> AsNoTracking<TEntity>(
[NotNull] this IQueryable<TEntity> source)
where TEntity : class
{
Check.NotNull(source, nameof(source));
return
source.Provider is EntityQueryProvider
? source.Provider.CreateQuery<TEntity>(
Expression.Call(
instance: null,
method: AsNoTrackingMethodInfo.MakeGenericMethod(typeof(TEntity)),
arguments: source.Expression))
: source;
}
Since the mocked DbSet<> i provide during the test, the Provider is IQueryable the function AsNoTracking should return the input source since "source.Provider is EntityQueryProvider" is false.
The only thing I couldn't check was Check.NotNull(source, nameof(source)); since I could not find what it does? if some has a explanation or code showing what it does I would appreciate it if you could share it with me.
Workaround:
The only workaround i found in the internet is from #cdwaddell in the thread https://github.com/aspnet/EntityFrameworkCore/issues/7937 who basically wrote his own gated version of AsNoTracking(). Using the workaround leads to success, but I wouldn't want to implement it as it seems to not check for something?
public static class QueryableExtensions
{
public static IQueryable<T> AsGatedNoTracking<T>(this IQueryable<T> source) where T : class
{
if (source.Provider is EntityQueryProvider)
return source.AsNoTracking<T>();
return source;
}
}
So, my questions:
Is with my workaround the only way to test stuff like this?
Is there a possibility to Mock this?
What does Check.NotNull(source, nameof(source)); in
AsNoTracking() do?
Do not mock DataContext.
DataContext is implementation details of access layer. Entity Framework Core provide two options for writing tests with DataContext dependencies without actual database.
In-Memory database - Testing with InMemory
SQLite in-memory - Testing with SQLite
Why you shouldn't mock DataContext?
Simply because with mocked DataContext you will test only that method called in expected order.
Instead in tests your should test behaviour of the code, returned values, changed state(database updates).
When you test behaviour you will be able refactor/optimise your code without rewriting tests for every change in the code.
In case In-memory tests didn't provide required behaviour - test your code against actual database.
Old question, but some people keep getting trouble (like me some hours ago!). I'll only let a brief explanation from official docs, that are well covered about unit testing databases. .AsNoTracking() is a query functionality, as observed in its source code:
public static IQueryable AsNoTracking(this IQueryable source)
{
var asDbQuery = source as DbQuery;
return asDbQuery != null ? asDbQuery.AsNoTracking() : CommonAsNoTracking(source);
}
Well, as mentioned in the MSDN:
However, 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.
There are others ways to unit testing EF Core. Using SQLite in memory is one of the bests. Please, read this topic, that is very useful for many contexts.
I have implemented repository pattern and unit of work on top of Data Access Layer. I have all the crud operation in Generic Repository but save method in unit of work. In my business class I am passing object to generic class, followed by Save method in unit of work, my question is how can i get scope ID from this point forward
Basically I need to get ID of object after saving where I don't know what is object ID name as I am using generic class to save data
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
protected DbSet<TEntity> _DbSet;
private readonly DbContext _dbContext;
public GenericRepository()
{ }
public GenericRepository(DbContext dbContext)
{
this._dbContext = dbContext;
_DbSet = _dbContext.Set<TEntity>();
}
public void InsertEntity(TEntity obj)
{
_DbSet.Add(obj);
}
}
...
public class FunctionsNavigation_UnitOfWork : IDisposable
{
private FunctionContext _FunctionContext = new FunctionContext();
uow.Qualification_FeeSchemeRepository.InsertEntity(obj);
}
}
public void Save()
{
_FunctionContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
_FunctionContext.SaveChanges();
}
in following business class after saving I try to get ID on object but I am not getting any thing
_uof.Sys_Nav_Functions_Repository.InsertEntity(_Sys_Nav_FunctionEntity);
_uof.Save();
_FunctionID = _obj.Sys_Nav_Function.Function_ID;
You will get the inserted Id at the object itself.
private FunctionContext _FunctionContext = new FunctionContext();
var obj = new yourEntity();
uow.Qualification_FeeSchemeRepository.InsertEntity(obj);
uow.Save();
Once you save the data into the database. The entity-framework will fill the entity type PK that is generated from the database.
In short you get here
int id = obj.Id;
Update Inserting 1:1 Relationship Sample
Person person = new Person//create person entity
{
FirstName = "Eldho",
LastName = "Abe",
};
AuthorizedUser user = new AuthorizedUser//create authorized user role entity
{
Person = person, //The reference of newly inserted user
UserId = myUserid,
HashedPassword = password,
};
uow.PersonDA.Insert(person);
uow.AuthorizedUserDA.Insert(user);
uow.Save();//insert to database
A bit of background, I have 2 tables in a database that share a many-to-many relationship through a junction table. Almost identical to the scenario outlined in this blog post.
I will use a very simplified example of what I'm trying to do. When a user on my web app wants to create an Order, they have access to a list of Products to add to the order before posting. This list of products is fetched trough a REST api call and is made available to the client app as JSON data.
My problem arises when I post an Order, Entity frame work tries to re-save the Products in my collection rather that just create the association.
Consider the following extremely simplified code sample of my controller and my UnitOfWork class.
public class OrdersController : ApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly IOrderRepository _orderRepository;
public OrdersController(IUnitOfWork unitOfWork, IOrderRepository orderRepository)
{
_unitOfWork = unitOfWork;
_orderRepository = orderRepository;
}
// POST api/Orders
public Dto.Get.Order Post(Dto.Post.Order postOrder)
{
Models.Order modelOrder = new Model.Order();
modelOrder.Name = postOrder.Name;
modelOrder.Description = postOrder.Description;
foreach (var getProduct in postOrder.Products)
{
Models.Product modelProduct = new Models.Product();
modelProduct.Id = getProduct.Id;
getProduct.Name = getProduct.Name;
getProduct.Price = getProduct.Price;
modelOrder.Products.Add(modelProduct);
}
modelOrder.AccountId = 999;
_orderRepository.Insert(modelOrder);
_orderRepository.Save();
_unitOfWork.SaveChanges();
//Return new Order as it exists in DB
return Get(modelOrder.Id);
}
}
public class UnitOfWork : IUnitOfWork
{
public MyApplicationContainer Context { get; set; }
public UnitOfWork(MyApplicationContainer context)
{
Context = context;
this.Context.Configuration.ProxyCreationEnabled = false;
this.Context.Configuration.LazyLoadingEnabled = false;
}
public void SaveChanges()
{
Context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
if (disposing)
Context.Dispose();
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Note: For simplicity, I didn't bother to post my Post and Get DTOs, or my Models for the Order and Product entities.
One interesting observation, if I fetch the model version of the Product based on the incoming ID and then assign it to the collection to be saved, it works fine. But that seems very "chatty" to me, considering an Order could potentially have hundreds of Products. Example:
foreach (var getProduct in postOrder.Products)
{
var modelProduct = _productRepository.Get(getProduct.Id);
modelOrder.Products.Add(modelProduct);
}
You are explicitly creating a new product with the new operator. Obviously EF tries to create new products in the database. You need to query the database for the products but you should do it with one query. Something along the lines of
var productsIDs = postOrder.Products.Select(p => p.ID).ToArray();
var actualProducts = from p in product
where productsIDs.Contains(p.ID)
select p;
I am using Entity Framework 4.3 and I am trying to reference an existing entity by setting the navigation property when creating a new entity, however when i call save EF complains that there is a PK violation in the table for which i set the navigation property to (i.e. it is creating a new record as opposed to a reference!).
How can i attach to an existing POCO as opposed to referencing it and having EF trying to create a new database record (but not simply use an ID, ideally i would like to reference an actual entity that came from another query)?
Thanks in advance,
Chris
public class BusinessUnit
{
public int BusinessUnitID { get; set; }
public ExternalPlugin AccountsDataSourceModule { get; set; }
public ExternalPlugin OptionalContactsDataSourceModule { get; set; }
}
public BusinessUnit NewBusinessUnit(string name, ExternalPlugin accountsModuleId = null, ExternalPlugin contactsModuleId = null)
{
IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork();
BusinessUnit unit = new BusinessUnit();
unit.CompanyName = name;
unit .AccountsDataSourceModule = accountsModuleId; // this causes a problem
unit .OptionalContactsDataSourceModule = contactsModuleId; // as does this
unitOfWork.BusinessUnitRepository.Insert(unit);
unitOfWork.Save();
return unit;
}
You must attach the existing entities to the context:
BusinessUnit unit = new BusinessUnit();
unit.CompanyName = name;
unitOfWork.ExternalPluginRepository.Attach(accountsModuleId);
unitOfWork.ExternalPluginRepository.Attach(contactsModuleId);
unit.AccountsDataSourceModule = accountsModuleId;
unit.OptionalContactsDataSourceModule = contactsModuleId;
unitOfWork.BusinessUnitRepository.Insert(unit);
...where unitOfWork.ExternalPluginRepository.Attach(ExternalPlugin plugin) must do:
context.ExternalPlugins.Attach(plugin);
I expect that all repositories use the same context instance. Attach tells EF that the plugins already exist in the database and avoids an INSERT of those entities.
Edit
If you get the error message...
An entity object cannot be referenced by multiple instances of
IEntityChangeTracker.
...it means that you have an entity that is attached to more than one context instance at the same time. You can avoid that in most cases by disposing the context always when you don't need it anymore. Your code sample does not follow this good practice. It rather should look like this:
public BusinessUnit NewBusinessUnit(string name,
ExternalPlugin accountsModuleId = null,
ExternalPlugin contactsModuleId = null)
{
using (IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork())
{
BusinessUnit unit = new BusinessUnit();
unit.CompanyName = name;
unitOfWork.ExternalPluginRepository.Attach(accountsModuleId);
unitOfWork.ExternalPluginRepository.Attach(contactsModuleId);
unit.AccountsDataSourceModule = accountsModuleId;
unit.OptionalContactsDataSourceModule = contactsModuleId;
unitOfWork.BusinessUnitRepository.Insert(unit);
unitOfWork.Save();
return unit;
}
}
At the end of the using block the Dispose method of the unitOfWork is called automatically. To get this working (and compiling as well) you need to derive your IUnitOfWork interface from IDisposable and implement it in the concrete UnitOfWork class:
public interface IUnitOfWork : IDisposable
{
// ...
}
public class ConcreteUnitOfWork : IUnitOfWork, IDisposable
{
private MyDbContext _context;
// I assume that you have a member for the DbContext in this class
// ...
// implementation of IDisposable
public void Dispose()
{
if (_context != null)
_context.Dispose();
}
}
I'm trying to implement the repository pattern using entity framework code first rc 1. The problem I am running into is with creating the DbContext. I have an ioc container resolving the IRepository and it has a contextprovider which just news up a new DbContext with a connection string in a windsor.config file. With linq2sql this part was no problem but EF seems to be choking. I'll describe the problem below with an example. I've pulled out the code to simplify things a bit so that is why you don't see any repository pattern stuff here. just sorta what is happening without all the extra code and classes.
using (var context = new PlssContext())
{
var x = context.Set<User>();
var y = x.Where(u => u.UserName == LogOnModel.UserName).FirstOrDefault();
}
using (var context2 = new DbContext(#"Data Source=.\SQLEXPRESS;Initial Catalog=PLSS.Models.PlssContext;Integrated Security=True;MultipleActiveResultSets=True"))
{
var x = context2.Set<User>();
var y = x.Where(u => u.UserName == LogOnModel.UserName).FirstOrDefault();
}
PlssContext is where I am creating my DbContext class. The repository pattern doesn't know anything about PlssContext. The best I thought I could do was create a DbContext with the connection string to the sqlexpress database and query the data that way. The connection string in the var context2 was grabbed from the context after newing up the PlssContext object. So they are pointing at the same sqlexpress database.
The first query works. The second query fails miserably with this error:
The model backing the 'DbContext'
context has changed since the database
was created. Either manually
delete/update the database, or call
Database.SetInitializer with an
IDatabaseInitializer instance. For
example, the
DropCreateDatabaseIfModelChanges
strategy will automatically delete and
recreate the database, and optionally
seed it with new data.
on this line
var y = x.Where(u => u.UserName == LogOnModel.UserName).FirstOrDefault();
Here is my DbContext
namespace PLSS.Models
{
public class PlssContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Corner> Corners { get; set; }
public DbSet<Lookup_County> Lookup_County { get; set; }
public DbSet<Lookup_Accuracy> Lookup_Accuracy { get; set; }
public DbSet<Lookup_MonumentStatus> Lookup_MonumentStatus { get; set; }
public DbSet<Lookup_CoordinateSystem> Lookup_CoordinateSystem { get; set; }
public class Initializer : DropCreateDatabaseAlways<PlssContext>
{
protected override void Seed(PlssContext context)
{
I've tried all of the Initializer strategies with the same errors. I don't think the database is changing. If I remove the
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
Then the error returns is
The entity type User is not part of the model for the current context.
Which sort of makes sense. But how do you bring this all together?
That is correct behavior. Plain DbContext has no idea about mappings (= doesn't know any of your entities). That is the reason why you should always create derived context. Your repository doesn't know about PlssContext but you can still inject it like:
public class Repository
{
private readonly DbContext _context;
public Repository(DbContext context)
{
_context = context;
}
...
}
var repository = new Repository(new PlssContext());
You can't use base DbContext instance directly when using code first.