Ich versuche, einen Komponententest für meinen Dienst mit einem verspotteten DbContext zu erstellen. Ich habe eine Schnittstelle IDbContext
mit folgenden Funktionen erstellt:
public interface IDbContext : IDisposable
{
IDbSet<T> Set<T>() where T : class;
DbEntityEntry<T> Entry<T>(T entity) where T : class;
int SaveChanges();
}
Mein realer Kontext implementiert diese Schnittstelle IDbContext
und DbContext
.
Jetzt versuche ich, das IDbSet<T>
im Kontext zu verspotten , also gibt es List<User>
stattdessen ein zurück.
[TestMethod]
public void TestGetAllUsers()
{
// Arrange
var mock = new Mock<IDbContext>();
mock.Setup(x => x.Set<User>())
.Returns(new List<User>
{
new User { ID = 1 }
});
UserService userService = new UserService(mock.Object);
// Act
var allUsers = userService.GetAllUsers();
// Assert
Assert.AreEqual(1, allUsers.Count());
}
Ich bekomme immer diesen Fehler auf .Returns
:
The best overloaded method match for
'Moq.Language.IReturns<AuthAPI.Repositories.IDbContext,System.Data.Entity.IDbSet<AuthAPI.Models.Entities.User>>.Returns(System.Func<System.Data.Entity.IDbSet<AuthAPI.Models.Entities.User>>)'
has some invalid arguments
Antworten:
Ich habe es geschafft, es zu lösen, indem ich eine
FakeDbSet<T>
Klasse erstellt habe, die implementiertIDbSet<T>
public class FakeDbSet<T> : IDbSet<T> where T : class { ObservableCollection<T> _data; IQueryable _query; public FakeDbSet() { _data = new ObservableCollection<T>(); _query = _data.AsQueryable(); } public virtual T Find(params object[] keyValues) { throw new NotImplementedException("Derive from FakeDbSet<T> 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<T>(); } public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T { return Activator.CreateInstance<TDerivedEntity>(); } public ObservableCollection<T> 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<T> IEnumerable<T>.GetEnumerator() { return _data.GetEnumerator(); } }
Jetzt sieht mein Test so aus:
[TestMethod] public void TestGetAllUsers() { //Arrange var mock = new Mock<IDbContext>(); mock.Setup(x => x.Set<User>()) .Returns(new FakeDbSet<User> { new User { ID = 1 } }); UserService userService = new UserService(mock.Object); // Act var allUsers = userService.GetAllUsers(); // Assert Assert.AreEqual(1, allUsers.Count()); }
quelle
async
EF-Überladungen siehe hierDanke Gaui für deine tolle Idee =)
Ich habe einige Verbesserungen an Ihrer Lösung hinzugefügt und möchte sie teilen.
FakeDbSet
erbt auch vonDbSet
zusätzlichen Methoden wieAddRange()
ObservableCollection<T>
mitList<T>
alle passieren bereits implementierten Methoden inList<>
bis zu meinemFakeDbSet
Mein FakeDbSet:
public class FakeDbSet<T> : DbSet<T>, IDbSet<T> where T : class { List<T> _data; public FakeDbSet() { _data = new List<T>(); } public override T Find(params object[] keyValues) { throw new NotImplementedException("Derive from FakeDbSet<T> 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<T>(); } public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T { return Activator.CreateInstance<TDerivedEntity>(); } public List<T> Local { get { return _data; } } public override IEnumerable<T> AddRange(IEnumerable<T> entities) { _data.AddRange(entities); return _data; } public override IEnumerable<T> RemoveRange(IEnumerable<T> 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<T> IEnumerable<T>.GetEnumerator() { return _data.GetEnumerator(); } }
Es ist sehr einfach, das dbSet zu ändern und das EF-Kontextobjekt zu verspotten:
var userDbSet = new FakeDbSet<User>(); userDbSet.Add(new User()); userDbSet.Add(new User()); var contextMock = new Mock<MySuperCoolDbContext>(); contextMock.Setup(dbContext => dbContext.Users).Returns(userDbSet);
Jetzt ist es möglich, Linq-Abfragen auszuführen. Beachten Sie jedoch, dass Fremdschlüsselreferenzen möglicherweise nicht automatisch erstellt werden:
var user = contextMock.Object.Users.SingeOrDefault(userItem => userItem.Id == 42);
Da das
Context.SaveChanges()
Kontextobjekt verspottet ist, werden keine Änderungen vorgenommen, und Eigenschaftsänderungen Ihrer Entites werden möglicherweise nicht in Ihr dbSet eingetragen. Ich habe dies gelöst, indem ich meineSetModifed()
Methode zum Auffüllen der Änderungen verspottet habe .quelle
Falls noch jemand interessiert ist, hatte ich das gleiche Problem und fand diesen Artikel sehr hilfreich: Testen von Entity Frameworks mit einem Mocking Framework (ab EF6)
Es gilt nur für Entity Framework 6 oder höher, deckt jedoch alles ab, von einfachen SaveChanges-Tests bis hin zu asynchronen Abfragetests, die alle Moq (und einige manuelle Klassen) verwenden.
quelle
Wenn noch jemand nach Antworten sucht, habe ich eine kleine Bibliothek implementiert , um DbContext verspotten zu können.
Schritt 1
Installieren Sie das Coderful.EntityFramework.Testing- Nuget-Paket:
Install-Package Coderful.EntityFramework.Testing
Schritt 2
Erstellen Sie dann eine Klasse wie folgt:
internal static class MyMoqUtilities { public static MockedDbContext<MyDbContext> MockDbContext( IList<Contract> contracts = null, IList<User> users = null) { var mockContext = new Mock<MyDbContext>(); // 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<SourcingDbContext>(mockContext, dbSets); } }
Schritt 3
Jetzt können Sie ganz einfach Mocks erstellen:
// Create test data. var contracts = new List<Contract> { new Contract("#1"), new Contract("#2") }; var users = new List<User> { new User("John"), new User("Jane") }; // Create DbContext with the predefined test data. var dbContext = MyMoqUtilities.MockDbContext( contracts: contracts, users: users).DbContext.Object;
Und dann benutze dein Mock:
// 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();
Vollständiger Artikel: http://www.22bugs.co/post/Mocking-DbContext/
quelle
Basierend auf diesem MSDN- Artikel habe ich meine eigenen Bibliotheken zum Verspotten erstellt
DbContext
undDbSet
:Beide sind auf NuGet und GitHub verfügbar.
Der Grund, warum ich diese Bibliotheken erstellt habe, ist, dass ich das
SaveChanges
Verhalten emulieren ,DbUpdateException
beim Einfügen von Modellen mit demselben Primärschlüssel ein auslösen und mehrspaltige / automatisch inkrementierte Primärschlüssel in den Modellen unterstützen wollte.Da darüber hinaus beide
DbSetMock
undDbContextMock
vererbenMock<DbSet>
undMock<DbContext
können Sie alle Funktionen des verwenden Moq Rahmen .Neben Moq gibt es auch eine NSubstitute-Implementierung.
Die Verwendung mit der Moq-Version sieht folgendermaßen aus:
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<User> 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<TestDbContext>("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); }
quelle
UpdateFromQueryAsync()
von zzProjects? entityframework-extensions.net/update-from-queryIch bin spät dran , fand diesen Artikel aber hilfreich: Testen mit InMemory (MSDN Docs).
Es wird erläutert, wie Sie einen In-Memory-DB-Kontext (der keine Datenbank ist) mit dem Vorteil einer sehr geringen Codierung und der Möglichkeit, Ihre
DBContext
Implementierung tatsächlich zu testen , verwenden.quelle