Mocking EF DbContext con Moq

Sto provando a creare un unit test per il mio servizio con un DbContext deriso. Ho creato un’interfaccia IDbContext con le seguenti funzioni:

 public interface IDbContext : IDisposable { IDbSet Set() where T : class; DbEntityEntry Entry(T entity) where T : class; int SaveChanges(); } 

Il mio vero contesto implementa questa interfaccia IDbContext e DbContext .

Ora sto provando a IDbSet nel contesto, quindi restituisce invece una List .

 [TestMethod] public void TestGetAllUsers() { // Arrange var mock = new Mock(); mock.Setup(x => x.Set()) .Returns(new List { new User { ID = 1 } }); UserService userService = new UserService(mock.Object); // Act var allUsers = userService.GetAllUsers(); // Assert Assert.AreEqual(1, allUsers.Count()); } 

Ricevo sempre questo errore. .Returns :

 The best overloaded method match for 'Moq.Language.IReturns<AuthAPI.Repositories.IDbContext,System.Data.Entity.IDbSet>.Returns(System.Func<System.Data.Entity.IDbSet>)' has some invalid arguments 

Sono riuscito a risolverlo creando una FakeDbSet IDbSet che implementa IDbSet

 public class FakeDbSet : IDbSet where T : class { ObservableCollection _data; IQueryable _query; public FakeDbSet() { _data = new ObservableCollection(); _query = _data.AsQueryable(); } public virtual T Find(params object[] keyValues) { throw new NotImplementedException("Derive from FakeDbSet and override Find"); } public T Add(T item) { _data.Add(item); return item; } public T Remove(T item) { _data.Remove(item); return item; } public T Attach(T item) { _data.Add(item); return item; } public T Detach(T item) { _data.Remove(item); return item; } public T Create() { return Activator.CreateInstance(); } public TDerivedEntity Create() where TDerivedEntity : class, T { return Activator.CreateInstance(); } public ObservableCollection Local { get { return _data; } } Type IQueryable.ElementType { get { return _query.ElementType; } } System.Linq.Expressions.Expression IQueryable.Expression { get { return _query.Expression; } } IQueryProvider IQueryable.Provider { get { return _query.Provider; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } } 

Ora il mio test assomiglia a questo:

 [TestMethod] public void TestGetAllUsers() { //Arrange var mock = new Mock(); mock.Setup(x => x.Set()) .Returns(new FakeDbSet { new User { ID = 1 } }); UserService userService = new UserService(mock.Object); // Act var allUsers = userService.GetAllUsers(); // Assert Assert.AreEqual(1, allUsers.Count()); } 

Grazie Gaui per la tua grande idea =)

Ho aggiunto alcuni miglioramenti alla tua soluzione e voglio condividerla.

  1. My FakeDbSet anche da DbSet per ottenere metodi aggiuntivi come AddRange()
  2. Ho sostituito ObservableCollection con List per passare tutti i metodi già implementati in List<> al mio FakeDbSet

My FakeDbSet:

  public class FakeDbSet : DbSet, IDbSet where T : class { List _data; public FakeDbSet() { _data = new List(); } public override T Find(params object[] keyValues) { throw new NotImplementedException("Derive from FakeDbSet and override Find"); } public override T Add(T item) { _data.Add(item); return item; } public override T Remove(T item) { _data.Remove(item); return item; } public override T Attach(T item) { return null; } public T Detach(T item) { _data.Remove(item); return item; } public override T Create() { return Activator.CreateInstance(); } public TDerivedEntity Create() where TDerivedEntity : class, T { return Activator.CreateInstance(); } public List Local { get { return _data; } } public override IEnumerable AddRange(IEnumerable entities) { _data.AddRange(entities); return _data; } public override IEnumerable RemoveRange(IEnumerable entities) { for (int i = entities.Count() - 1; i >= 0; i--) { T entity = entities.ElementAt(i); if (_data.Contains(entity)) { Remove(entity); } } return this; } Type IQueryable.ElementType { get { return _data.AsQueryable().ElementType; } } Expression IQueryable.Expression { get { return _data.AsQueryable().Expression; } } IQueryProvider IQueryable.Provider { get { return _data.AsQueryable().Provider; } } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } } 

È molto semplice modificare dbSet e Mock dell’object di contesto EF:

  var userDbSet = new FakeDbSet(); userDbSet.Add(new User()); userDbSet.Add(new User()); var contextMock = new Mock(); contextMock.Setup(dbContext => dbContext.Users).Returns(userDbSet); 

Ora è ansible eseguire le interrogazioni Linq, ma bisogna essere consapevoli che i riferimenti alle chiavi esterne potrebbero non essere creati automaticamente:

  var user = contextMock.Object.Users.SingeOrDefault(userItem => userItem.Id == 42); 

Poiché l’object di contesto viene preso in giro, Context.SaveChanges Context.SaveChanges() non eseguirà nulla e le modifiche alle proprietà delle tue entrate potrebbero non essere inserite nel tuo dbSet. Ho risolto questo problema SetModifed() giro il mio metodo SetModifed() per popolare le modifiche.

Nel caso qualcuno sia ancora interessato, ho riscontrato lo stesso problema e ho trovato questo articolo molto utile: Entity Framework Testing with a Mocking Framework (EF6 in poi)

Si applica solo a Entity Framework 6 o più recente, ma copre tutto, dai semplici test di SaveChanges al test di query asincrone, tutto utilizzando Moq (e alcune delle classi manuali).

Se qualcuno è ancora alla ricerca di risposte, ho implementato una piccola libreria per consentire il docking di DbContext.

passo 1

Installa Coderful.EntityFramework.Testing pacchetto nuget:

Install-Package Coderful.EntityFramework.Testing

passo 2

Quindi crea una class come questa:

 internal static class MyMoqUtilities { public static MockedDbContext MockDbContext( IList contracts = null, IList users = null) { var mockContext = new Mock(); // Create the DbSet objects. var dbSets = new object[] { MoqUtilities.MockDbSet(contracts, (objects, contract) => contract.ContractId == (int)objects[0] && contract.AmendmentId == (int)objects[1]), MoqUtilities.MockDbSet(users, (objects, user) => user.Id == (int)objects[0]) }; return new MockedDbContext(mockContext, dbSets); } } 

passaggio 3

Ora puoi creare mock super facilmente:

 // Create test data. var contracts = new List { new Contract("#1"), new Contract("#2") }; var users = new List { new User("John"), new User("Jane") }; // Create DbContext with the predefined test data. var dbContext = MyMoqUtilities.MockDbContext( contracts: contracts, users: users).DbContext.Object; 

E poi usa la tua finta:

 // Create. var newUser = dbContext.Users.Create(); // Add. dbContext.Users.Add(newUser); // Remove. dbContext.Users.Remove(someUser); // Query. var john = dbContext.Users.Where(u => u.Name == "John"); // Save changes won't actually do anything, since all the data is kept in memory. // This should be ideal for unit-testing purposes. dbContext.SaveChanges(); 

Articolo completo: http://www.22bugs.co/post/Mocking-DbContext/

Basato su questo articolo di MSDN , ho creato la mia libreria chiamata EntityFrameworkMock per il EntityFrameworkMock di DbContext e DbSet . Disponibile su NuGet e github.

Attualmente è solo EF6, perché è quello che sto usando in questo momento. Probabilmente può essere trasformato in un progetto multi-target per raggiungere anche .NET Core.

Il motivo per cui ho creato questa libreria è perché volevo emulare il comportamento di SaveChanges , lanciare DbUpdateException quando si inseriscono modelli con la stessa chiave primaria e supportare chiavi primarie multi-colonna / auto-incremento nei modelli.

Inoltre, poiché DbSetMock e DbContextMock ereditano da Mock e Mock , è ansible utilizzare tutte le funzionalità del framework Moq .

L'utilizzo è simile a questo:

 public class User { [Key, Column(Order = 0)] public Guid Id { get; set; } public string FullName { get; set; } } public class TestDbContext : DbContext { public TestDbContext(string connectionString) : base(connectionString) { } public virtual DbSet Users { get; set; } } [TestFixture] public class MyTests { var initialEntities = new[] { new User { Id = Guid.NewGuid(), FullName = "Eric Cartoon" }, new User { Id = Guid.NewGuid(), FullName = "Billy Jewel" }, }; var dbContextMock = new DbContextMock("fake connectionstring"); var usersDbSetMock = dbContextMock.CreateDbSetMock(x => x.Users, initialEntities); // Pass dbContextMock.Object to the class/method you want to test // Query dbContextMock.Object.Users to see if certain users were added or removed // or use Mock Verify functionality to verify if certain methods were called: usersDbSetMock.Verify(x => x.Add(...), Times.Once); }