我有一个解决方案,其中我有一个Data项目,其中包含从现有数据库生成的EF6 .edmx文件。我将实体拆分为一个单独的实体项目,并有一个存储库项目,它们都引用它们。
我添加了一个带有一些常用方法的BaseRepository,并希望对它进行单元测试。班级的顶端看起来像这样......
public class BaseRepository<T> : BaseRepositoryInterface<T> where T : class {
private readonly MyEntities _ctx;
private readonly DbSet<T> _dbSet;
public BaseRepository(MyEntities ctx) {
_ctx = ctx;
_dbSet = _ctx.Set<T>();
}
public IEnumerable<T> GetAll() {
return _dbSet;
}
//...
}
按照我在https://stackoverflow.com/a/21074664/706346找到的代码,我尝试了以下内容......
[TestMethod]
public void BaseRepository_GetAll() {
IDbSet<Patient> mockDbSet = Substitute.For<IDbSet<Patient>>();
mockDbSet.Provider.Returns(GetPatients().Provider);
mockDbSet.Expression.Returns(GetPatients().Expression);
mockDbSet.ElementType.Returns(GetPatients().ElementType);
mockDbSet.GetEnumerator().Returns(GetPatients().GetEnumerator());
MyEntities mockContext = Substitute.For<MyEntities>();
mockContext.Patients.Returns(mockDbSet);
BaseRepositoryInterface<Patient> patientsRepository
= new BaseRepository<Patient>(mockContext);
List<Patient> patients = patientsRepository.GetAll().ToList();
Assert.AreEqual(GetPatients().Count(), patients.Count);
}
private IQueryable<Patient> GetPatients() {
return new List<Patient> {
new Patient {
ID = 1,
FirstName = "Fred",
Surname = "Ferret"
}
}
.AsQueryable();
}
请注意,我更改了上下文TT文件以使用IDbSet,正如Stuart Clement在12月4日15日在22:41发表的评论中所建议的
但是,当我运行此测试时,它会给出一个空引用异常,因为基础知识库构造函数中设置_dbSet
的行会使其为空...
_dbSet = _ctx.Set<T>();
我猜我在设置模拟上下文时需要添加另一行,但我不确定是什么。我认为上面的代码应该足以填充DbSet。
任何人都能解释我错过了什么或做错了什么?
答案 0 :(得分:5)
好吧,我试图按照我在问题中展示的方式疯狂地试图去做,我遇到Effort,这是专为此任务而设计的,然后跟着this tutorial,让我走了。我的代码存在一些问题,我将在下面解释。
简单地说,我所做的是......
*)在测试项目中安装Effort.EF6。我一开始犯了一个错误并安装了Effort(没有EF6位),并且遇到了各种各样的问题。如果您正在使用EF6(或我认为的EF5),请确保安装此版本。
*)修改了MyModel.Context.tt文件以包含一个带有DbConnection的额外构造函数... public MyEntities(DbConnection connection) : base(connection, true) { }
*)将连接字符串添加到测试项目的App.Config文件中。我从数据项目中逐字复制了这个。
*)为测试类添加了初始化方法以设置上下文...
private MyEntities _ctx;
private BaseRepository<Patient> _patientsRepository;
private List<Patient> _patients;
[TestInitialize]
public void Initialize() {
string connStr = ConfigurationManager.ConnectionStrings["MyEntities"].ConnectionString;
DbConnection connection = EntityConnectionFactory.CreateTransient(connStr);
_ctx = new MyEntities(connection);
_patientsRepository = new BaseRepository<Patient>(_ctx);
_patients = GetPatients();
}
重要 - 在链接文章中,他使用DbConnectionFactory.CreateTransient()
,当我尝试运行测试时,它会出现异常。这似乎是针对Code First的,因为我使用Model First,我不得不将其更改为使用EntityConnectionFactory.CreateTransient()
。
*)实际测试相当简单。我添加了一些辅助方法来尝试整理它,并使其更具可重用性。在我完成之前,我可能会再进行几轮重构,但这样做有效,而且现在已经足够干净......
[TestMethod]
public void BaseRepository_Update() {
AddAllPatients();
Assert.AreEqual(_patients.Count, _patientsRepository.GetAll().Count());
}
#region Helper methods
private List<Patient> GetPatients() {
return Enumerable.Range(1, 10).Select(CreatePatient).ToList();
}
private static Patient CreatePatient(int id) {
return new Patient {
ID = id,
FirstName = "FirstName_" + id,
Surname = "Surname_" + id,
Address1 = "Address1_" + id,
City = "City_" + id,
Postcode = "PC_" + id,
Telephone = "Telephone_" + id
};
}
private void AddAllPatients() {
_patients.ForEach(p => _patientsRepository.Update(p));
}
#endregion
在这里需要思想转变的一点是,与Effort不同,与其他模拟不同,你不会告诉模拟框架为特定参数返回什么。相反,你必须把Effort想象成一个真正的数据库,尽管它是一个临时存储器。因此,我在初始化时建立了一个模拟患者列表,将它们添加到数据库中,然后才进行实际测试。
希望这有助于某人。事实证明,这比我最初尝试的方式要容易得多。
答案 1 :(得分:1)
我创建了一个NSubstitute扩展来帮助对存储库层进行单元测试,您可以在GitHub DbContextMockForUnitTests上找到它。您要引用的主文件是DbContextMockForUnitTests/MockHelpers/MockExtension.cs(它在用于使用--Parents whose children contain a subset of children
--setup
create table #parent ( id int )
create table #child ( parent_id int, foo varchar(32) )
insert into #parent (id) values (1)
insert into #parent (id) values (2)
insert into #parent (id) values (3)
insert into #child (parent_id, foo) values (1, 'buzz')
insert into #child (parent_id, foo) values (1, 'buzz')
insert into #child (parent_id, foo) values (1, 'fizz')
insert into #child (parent_id, foo) values (2, 'buzz')
insert into #child (parent_id, foo) values (2, 'fizz')
insert into #child (parent_id, foo) values (2, 'bang')
insert into #child (parent_id, foo) values (3, 'buzz')
--create in calling procedure
declare @tblTargets table (strTarget varchar(10))
insert into @tblTargets (strTarget) values ('fizz')
insert into @tblTargets (strTarget) values ('buzz')
--select query to be called in procedure;
-- pass @tblTargets in as TVP, or create from delimited string via splitter function
select #parent.id --returns 1 and 2
from #parent
inner join #child on #parent.id = #child.parent_id
where #child.foo in (select strTarget from @tblTargets)
group by #parent.id
having count(distinct #child.foo) = (select COUNT(*) from @tblTargets)
--cleanup
drop table #parent
drop table #child
进行测试的同一文件夹中有3个相关代码文件),将所有4个文件复制并粘贴到您的文件中项目。您可以看到此单元测试,其中显示了如何使用它DbContextMockForUnitTests/DbSetTests.cs。
为了使其与您的代码相关,我们假设您已复制主文件并在async
语句中引用了正确的命名空间。您的代码将是这样的(如果using
未被密封,您将不需要更改它,但我仍然认为编码的一般规则是尝试接受可能的最不具体的类型) :
MyEntities
单元测试代码:
// Slight change to BaseRepository, see comments
public class BaseRepository<T> : BaseRepositoryInterface<T> where T : class {
private readonly DbContext _ctx; // replaced with DbContext as there is no need to have a strong reference to MyEntities, keep it generic as possible unless there is a good reason not to
private readonly DbSet<T> _dbSet;
// replaced with DbContext as there is no need to have a strong reference to MyEntities, keep it generic as possible unless there is a good reason not to
public BaseRepository(DbContext ctx) {
_ctx = ctx;
_dbSet = _ctx.Set<T>();
}
public IEnumerable<T> GetAll() {
return _dbSet;
}
//...
}
免责声明 - 我是上述存储库的作者,但部分基于Testing with Your Own Test Doubles (EF6 onwards)