【发布时间】:2021-07-10 22:53:12
【问题描述】:
StackOverflow 上有很多类似主题的文章,但我没有找到专门针对我的情况的文章。
我正在尝试使用 Moq 和 NUnit 为通用存储库进行单元测试,并实现了工作单元模式。
这是通用存储库的代码:
public class GenericRepository<TContext, TEntity> : IGenericRepository<TEntity>
where TEntity : class
where TContext : DbContext
{
protected readonly TContext Context;
public GenericRepository(TContext context)
{
Context = context;
}
public async Task<TEntity> Create(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException();
}
await Task.Run(() => Context.Set<TEntity>().Add(entity)).ConfigureAwait(false);
await Context.SaveChangesAsync().ConfigureAwait(false);
return entity;
}
public async Task<IEnumerable<TEntity>> CreateRange(IEnumerable<TEntity> entities)
{
if (entities == null)
{
throw new ArgumentNullException();
}
await Task.Run(() => Context.Set<TEntity>().AddRange(entities)).ConfigureAwait(false);
await Context.SaveChangesAsync().ConfigureAwait(false);
return entities;
}
public async Task<TEntity> Retrieve(object entityId)
{
return await Context.Set<TEntity>().FindAsync(entityId).ConfigureAwait(false);
}
public async Task<IEnumerable<TEntity>> Retrieve(Expression<Func<TEntity, bool>> predicate)
{
return await Task.Run(() => Context.Set<TEntity>().Where(predicate)).ConfigureAwait(false);
}
public async Task<TEntity> Update(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException();
}
await Task.Run(() =>
{
Context.Set<TEntity>().AddOrUpdate(entity);
}).ConfigureAwait(false);
await Context.SaveChangesAsync().ConfigureAwait(false);
return entity;
}
public async Task<IEnumerable<TEntity>> UpdateRange(IEnumerable<TEntity> entities)
{
var updateRange = entities as TEntity[] ?? entities.ToArray();
if (updateRange.Any(entity => entity == null))
{
throw new ArgumentNullException();
}
foreach (var entity in updateRange)
{
await Task.Run(() =>
{
Context.Set<TEntity>().AddOrUpdate(entity);
}).ConfigureAwait(false);
}
await Context.SaveChangesAsync().ConfigureAwait(false);
return updateRange;
}
public async Task<TEntity> SafeDelete(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException();
}
var propertySet = TrySetProperty(entity, "Is_deleted", true);
if (!propertySet)
{
throw new InvalidOperationException();
}
await Task.Run(() =>
{
Context.Set<TEntity>().AddOrUpdate(entity);
}).ConfigureAwait(false);
await Context.SaveChangesAsync().ConfigureAwait(false);
return entity;
}
public async Task<IEnumerable<TEntity>> SafeDeleteRange(IEnumerable<TEntity> entities)
{
var safeDeleteRange = entities as TEntity[] ?? entities.ToArray();
if (safeDeleteRange.Any(entity => entity == null))
{
throw new ArgumentNullException();
}
var propertySet = false;
foreach (var entity in safeDeleteRange)
{
propertySet = TrySetProperty(entity, "Is_deleted", true);
}
if (!propertySet)
{
throw new InvalidOperationException();
}
foreach (var entity in safeDeleteRange)
{
await Task.Run(() =>
{
Context.Set<TEntity>().AddOrUpdate(entity);
}).ConfigureAwait(false);
}
await Context.SaveChangesAsync().ConfigureAwait(false);
return safeDeleteRange;
}
public async Task<TEntity> Delete(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException();
}
await Task.Run(() => Context.Set<TEntity>().Remove(entity));
await Context.SaveChangesAsync().ConfigureAwait(false);
return entity;
}
public async Task<IEnumerable<TEntity>> DeleteRange(IEnumerable<TEntity> entities)
{
if (entities == null)
{
throw new ArgumentNullException();
}
await Task.Run(() => Context.Set<TEntity>().RemoveRange(entities));
await Context.SaveChangesAsync().ConfigureAwait(false);
return entities;
}
private static bool TrySetProperty(object obj, string property, object value)
{
var prop = obj.GetType().GetProperty(property, BindingFlags.Public | BindingFlags.Instance);
if (prop == null || !prop.CanWrite)
{
return false;
}
prop.SetValue(obj, value, null);
return true;
}
}
还有一个单元测试类:
[TestFixture]
public class GenericRepositoryUnitTests
{
private Mock<IDomainRepository> _repo;
private Mock<IUnitOfWork> _unitOfWork;
private Mock<IUnitOfWorkFactory<DmsDbContext>> _unitOfWorkFactory;
private Mock<DmsDbContext> _mockContext;
private Mock<DbSet<Domain>> _mockSet;
private List<Domain> _domains;
private User _user;
[SetUp]
public void SetUp()
{
var userId = Guid.NewGuid();
_user = new User
{
UsersId = userId,
Username = "Test",
Password = "Test",
First_name = "Test",
Last_name = "Test",
Img = "Test",
Permissions = "Test",
Is_deleted = false
};
_domains = new List<Domain>
{
new Domain
{
DomainId = Guid.NewGuid(),
Name = "Test",
Url = "Test",
Is_deleted = false,
Users = new List<User>
{
_user,
new User
{
UsersId = userId,
Username = "Test",
Password = "Test",
First_name = "Test",
Last_name = "Test",
Img = "Test",
Permissions = "Test",
Is_deleted = false
},
new User
{
UsersId = Guid.NewGuid(),
Username = "Test",
Password = "Test",
First_name = "Test",
Last_name = "Test",
Img = "Test",
Permissions = "Test",
Is_deleted = false
}
}
},
new Domain
{
DomainId = Guid.NewGuid(),
Name = "Test",
Url = "Test",
Is_deleted = false,
Users = new List<User>
{
_user,
new User
{
UsersId = userId,
Username = "Test",
Password = "Test",
First_name = "Test",
Last_name = "Test",
Img = "Test",
Permissions = "Test",
Is_deleted = false
},
new User
{
UsersId = Guid.NewGuid(),
Username = "Test",
Password = "Test",
First_name = "Test",
Last_name = "Test",
Img = "Test",
Permissions = "Test",
Is_deleted = false
}
}
},
new Domain
{
DomainId = Guid.NewGuid(),
Name = "Test",
Url = "Test",
Is_deleted = false,
Users = new List<User>
{
_user,
new User
{
UsersId = userId,
Username = "Test",
Password = "Test",
First_name = "Test",
Last_name = "Test",
Img = "Test",
Permissions = "Test",
Is_deleted = false
},
new User
{
UsersId = Guid.NewGuid(),
Username = "Test",
Password = "Test",
First_name = "Test",
Last_name = "Test",
Img = "Test",
Permissions = "Test",
Is_deleted = false
}
}
}
};
_mockSet = new Mock<DbSet<Domain>>();
_mockSet.Setup(m => m.Add(It.IsAny<Domain>())).Returns(new Domain());
_mockContext = new Mock<DmsDbContext>();
_mockContext.Setup(m => m.Set<Domain>()).Returns(_mockSet.Object);
_repo = new Mock<IDomainRepository>{CallBase = true};
_repo.Setup(r => r.Context).Returns(_mockContext.Object);
_repo.Setup(r => r.Create(It.IsAny<Domain>())).Returns(Task.FromResult(new Domain()));
_repo.Setup(r => r.CreateRange(It.IsAny<IEnumerable<Domain>>()))
.Returns(Task.FromResult(new List<Domain>().AsEnumerable()));
_repo.Setup(r => r.Retrieve(It.IsAny<Guid>())).Returns(Task.FromResult(_domains.AsEnumerable().First()));
_repo.Setup(r => r.Retrieve(pre => pre.Is_deleted == false))
.Returns(Task.FromResult(_domains.AsEnumerable()));
_repo.Setup(r => r.Update(It.IsAny<Domain>())).Returns(Task.FromResult(new Domain()));
_repo.Setup(r => r.Delete(It.IsAny<Domain>())).Returns(Task.FromResult(new Domain()));
_repo.Setup(r => r.DeleteRange(It.IsAny<IEnumerable<Domain>>()))
.Returns(Task.FromResult(new List<Domain>().AsEnumerable()));
_unitOfWork = new Mock<IUnitOfWork>();
_unitOfWork.Setup(u => u.Domains).Returns(_repo.Object);
_unitOfWorkFactory = new Mock<IUnitOfWorkFactory<DmsDbContext>>();
_unitOfWorkFactory.Setup(u => u.Create(It.IsAny<DmsDbContext>())).Returns(_unitOfWork.Object);
}
[Test]
public async Task Create_NewDomainObject_AddToDatabase()
{
// Arrange
var domain = new Domain
{
DomainId = Guid.NewGuid(),
Name = "Test",
Url = "Test",
Is_deleted = false,
Users = new List<User> { _user }
};
// Act
using var unitOfWork = _unitOfWorkFactory.Object.Create(_mockContext.Object);
await unitOfWork.Domains.Create(domain);
// Assert
_repo.Verify(m => m.Context.Set<Domain>().Add(It.IsAny<Domain>()), Times.Once());
}
[Test]
public void Retrieve_TestDomainObjectById_ReturnsValue()
{
//Arrange
var domainId = Guid.NewGuid();
using var unitOfWork = _unitOfWorkFactory.Object.Create(_mockContext.Object);
// Act
var domain = unitOfWork.Domains.Retrieve(domainId);
// Assert
Assert.NotNull(domain);
}
我无法使这些测试正常工作,也不知道出了什么问题。
谁能解释一下,如何使用上面提到的一组工具正确测试通用存储库?
提前致谢:)
【问题讨论】:
-
您实际上根本没有测试任何东西。您所做的只是创建模拟对象并针对它们进行测试,以查看它们是否与您的具体实现方式相同(这永远不会起作用)。您需要测试一个实现您的通用存储库的存储库,并使用 moq 来模拟您的依赖项。
-
嘿@Hayden,感谢您的快速解释!单元测试的话题对我来说很新,所以你能提供一些通用存储库的正确单元测试的例子吗?我发现了很多,但它们仅与测试 Retrieve 方法有关。到目前为止,其余的对我来说都是一个谜:D 如果您可以为 Create 方法提供单元测试的精确实现,例如,那将非常好,而且会让我清楚地了解我做错了什么。跨度>
标签: c# entity-framework nunit moq unit-of-work