Moq로 EF DbContext 모의
모의 DbContext를 사용하여 서비스에 대한 단위 테스트를 만들려고합니다. IDbContext
다음과 같은 기능으로 인터페이스 를 만들었습니다 .
public interface IDbContext : IDisposable
{
IDbSet<T> Set<T>() where T : class;
DbEntityEntry<T> Entry<T>(T entity) where T : class;
int SaveChanges();
}
내 실제 시나리오는이 인터페이스 IDbContext
와 DbContext
.
이제 IDbSet<T>
컨텍스트에서 조롱하려고 하므로 List<User>
대신 를 반환합니다 .
[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());
}
나는 항상이 오류가 발생합니다 .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
FakeDbSet<T>
구현 하는 클래스를 만들어 해결했습니다.IDbSet<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();
}
}
이제 내 테스트는 다음과 달라집니다.
[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());
}
당신의 훌륭한 아이디어에 대해 Gaui에게 감사드립니다 =)
솔루션에 몇 가지 개선 사항을 추가하고 공유하고 싶습니다.
- 내
FakeDbSet
또한 다음DbSet
과 같은 추가 방법을 위해 내재AddRange()
- 나는 대체
ObservableCollection<T>
와가List<T>
의 모든 이미 구현 방법을 통과List<>
내에서FakeDbSet
내 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();
}
}
dbSet을 수정하고 EF Context Object를 모의하는 것은 매우 신뢰할 수 있습니다.
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);
이제 Linq는 쿼리를 수 없습니다 외래 키 참조가 자동으로 사용할 수 없습니다.
var user = contextMock.Object.Users.SingeOrDefault(userItem => userItem.Id == 42);
개체가 컨텍스트 Context.SaveChanges()
모의 처리 되었기 때문에 아무것도 수행하지 않으며 항목의 속성 변경 사항이 dbSet에 채워지지 않을 수 있습니다. 나는 SetModifed()
변경 사항을 채우기 위해 내 방법을 조롱하여 해결했습니다 .
누군가가 여전히 관심이있는 경우에 동일한 문제가 발생했다는 것이 문서가 매우 유용하다는 것을 의미 합니다. Mocking Framework를 Entity Framework Testing (EF6 이상)
Entity Framework 6 이상에만 적용하는 간단한 SaveChanges 테스트부터 Moq (및 몇 가지 수동 클래스)를 사용하는 말 쿼리 테스트까지 모든 것을 다룹니다.
누군가가 여전히 답변을 소유 한 DbContext를 조롱 할 수있는 작은 라이브러리 를 구현했습니다 .
1 단계
Coderful.EntityFramework.Testing 너겟 패키지를 설치 합니다.
Install-Package Coderful.EntityFramework.Testing
2 단계
그런 다음 다음과 같은 클래스를 만듭니다.
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);
}
}
3 단계
이제 모의를 매우 쉽게 만들 수 있습니다.
// 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;
그런 다음 모의를 사용하십시오.
// 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();
전체 기사 : http://www.22bugs.co/post/Mocking-DbContext/
이 MSDN 기사를 기반으로 모의 DbContext
및 DbSet
다음을 위한 자체 라이브러리를 만들었습니다 .
NuGet 및 GitHub에서 모두 사용할 수 있습니다.
이 라이브러리를 만든 이유는 SaveChanges
동작 을 에뮬레이트 DbUpdateException
하고 동일한 기본 키를 사용하여 모델을 삽입 할 때를 던지고 모델에서 다중 열 / 자동 증가 기본 키를 지원하기 때문입니다.
또한, 두 이후 DbSetMock
및 DbContextMock
상속 Mock<DbSet>
하고 Mock<DbContext
, 당신의 모든 기능을 사용할 수 있습니다 MOQ 프레임 워크를 .
Moq 옆에는 NSubstitute 구현도 있습니다.
Moq 버전에서의 사용법은 다음과 같습니다.
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);
}
늦었지만이 문서가 도움이되었다고 생각했습니다 : InMemory로 테스트 (MSDN 문서).
매우 적은 코딩의 이점과 실제로 DBContext
구현을 테스트 할 수있는 기회로 메모리 내 DB 컨텍스트 (데이터베이스가 아님)를 사용하는 방법을 설명합니다 .
참고 URL : https://stackoverflow.com/questions/25960192/mocking-ef-dbcontext-with-moq
'ProgramingTip' 카테고리의 다른 글
부트 가능한 그리드 레이아웃이 HTML 테이블보다 선호되는 이유는 무엇입니까? (0) | 2020.12.05 |
---|---|
ansible 플레이 북에서 user vs sudo vs sudo_user (0) | 2020.12.05 |
HTML의 첫 번째 줄에없는 토큰 < (0) | 2020.12.05 |
docker : 잘못된 참조 형식 (0) | 2020.12.05 |
Scala의 기본 생성자에서 로컬 var / val을 어떻게 정의합니까? (0) | 2020.12.05 |