我正在为我正在努力的项目编写自动化测试,以便更熟悉MVC,EntityFramework(代码优先),单元测试和Moq。
我的Repository
类中有一个部分,只要控制器调用LastModified
,就会设置模型的Repository.SaveChanges()
字段,其工作方式如下(MyModelBase是基类):
public void RepoSaveChanges()
{
foreach(var entry in _dbContext.ChangeTracker.Entities().Where(e => e.State == EntityState.Modified))
{
MyModelBase model = entry.Entity as MyModelBase;
if (model != null)
{
model.LastModified = DateTime.Now;
}
}
_dbContext.SaveChanges();
}
这在正常在线环境中的应用程序运行时期间工作正常,但在测试方法中运行时会中断。我使用Moq来模拟DbContext中的DbSets并设置我的测试数据。
这是我的奇怪部分:
我的单元测试运行正常(通过)但它们实际上并没有进入foreach
循环 - 当访问ChangeTracker.Entities()
时它会挂起并退出循环,跳到_dbContext.SaveChanges()
。没有错误。
但是,在与我共享项目的朋友的机器上,当访问ChangeTracker.Entities()
时,他会收到SQLException。我确实在VS2015中检查了SQLExceptions,并且没有输出或其他异常指示。
结果StackTrace:
在System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject,UInt32 waitForMultipleObjectsTimeout,Boolean allowCreate,Boolean onlyOneCheckConnection,DbConnectionOptions userOptions,DbConnectionInternal& connection) ....结果讯息:
测试方法MyProject.Tests.TestClasses.MyControllerTests.TestCreate抛出异常: System.Data.SqlClient.SqlException:建立与SQL Server的连接时发生与网络相关或特定于实例的错误。服务器未找到或无法访问。验证实例名称是否正确,以及SQL Server是否配置为允许远程连接。 (提供程序:SQL网络接口,错误:26 - 查找指定的服务器/实例时出错)
最后,我的问题是:有没有办法使用Moq模拟ChangeTracker(我怀疑不是来自之前的调查),还是我可以采取另一种方法来自动设置属性的RepoSaveChanges()
?在不访问ChangeTracker.Entities()
的情况下,我需要有更新逻辑来为每个拥有它的模型类型设置LastModified
字段。同样,我觉得避免使用该API /框架的一部分,因为测试很顽固并不理想。
有没有人知道为什么SQLException没有抛出/无法在我的机器上捕获?或者有关如何在单元测试中使用ChangeTracker.Entities()
的任何建议?我只会在我的所有模型和控制器上单独设置LastModified
属性作为最后的手段。
更新 已经请求了更多示例代码,所以让我进一步详细说明。我使用moq模拟DbContext,然后模拟DbContext中包含的DbSet对象:
var mockContext = new Mock<MyApplicationDbContext>(); //MyApplicationDbContext extends DbContext
Person p = new Person();
p.Name = "Bob";
p.Employer = "Superstore";
List<Person> list = new List<Person>();
list.Add(p);
var queryable = list.AsQueryable();
Mock<DbSet<Person>> mockPersonSet = new Mock<DbSet<Person>>();
mockPersonSet.As<IQueryable<Person>>().Setup(set => set.Provider).Returns(queryable.Provider);
mockPersonSet.As<IQueryable<Person>>().Setup(set => set.Expression).Returns(queryable.Expression);
mockPersonSet.As<IQueryable<Person>>().Setup(set => set.ElementType).Returns(queryable.ElementType);
mockPersonSet.As<IQueryable<Person>>().Setup(set => set.GetEnumerator()).Returns(() => queryable.GetEnumerator());
DbSet<Person> dbSet = mockPersonSet.Object as DbSet<Person>;
mockPersonSet.Setup(set => set.Local).Returns(dbSet.Local);
mockContext.Setup(context => context.Set<Person>()).Returns(mockPersonSet.Object);
mockContext.Setup(context => context.Persons).Returns(mockPersonSet.Object));
//Create the repo using the mock data context I created
PersonRepository repo = new PersonRepository(mockContext.Object);
//Then finally create the controller and perform the test
PersonController controller = new PersonController(repo);
var result = controller.Create(someEmployerID); //Sometimes throws an SQLException when DbContext.SaveChanges() is called
答案 0 :(得分:2)
我为自己找到了一个不太理想的解决方案,但足以让我继续前进。我通过简单地向我的类添加了一个抽象级别来绕过DbContext.ChangeTracker.Entries()
API,该类扩展了DbContext
MyApplicationDbContext
:
public class MyApplicationDbContext : IdentityDbContext<MyApplicationUser>
{
//DbSets etc
public virtual IEnumerable<MyModelBase> AddedEntries
{
get
{
foreach (var entry in ChangeTracker.Entries().Where(entry => entry.State == EntityState.Added))
{
MyModelBase model = entry.Entity as MyModelBase;
if (model != null)
{
yield return model;
}
}
}
}
}
这样我仍然可以通过调用MyApplicationDbContext.AddedEntries
而不是MyApplicationDbContext.ChangeTracker.Entries()
来迭代业务逻辑的Entries(),如问题陈述中所述。但是,由于我创建了属性virtual
,我可以使用Moq设置返回:
List<SomeModel> addedEntries = new List<SomeModel>();
addedEntries.add(someModelWhichWillBeAddedByTheController);
mockContext.Setup(context => context.AddedEntries).Returns(addedEntries);
这样,当使用someModelWhichWillBeAddedByTheController
属性时,控制器将观察AddedEntries
。缺点是我无法测试真实业务逻辑中使用的DbContext.ChangeTracker.Entries()
代码,但我稍后可以通过使用测试数据库实现集成测试来实现此目的。
我永远无法找到SQLException
被抛在一台机器而不是另一台机器上的原因。
答案 1 :(得分:0)
您的错误是说它无法创建与数据库的连接。在创建DbContext时会发生这种情况,其中EF将开始执行诸如检查是否需要迁移数据库之类的操作。
在我看来,您还需要模拟dbcontext的构造函数。我对moq本身并不太熟悉,但如果我正确地阅读了你的代码,我就不会看到你嘲笑构造函数。
答案 2 :(得分:0)
例外情况说它无法创建与DB的连接,我怀疑你和你的朋友在app.config文件中有不同的连接字符串,或者你的朋友无法访问数据库。
实际上您正在编写集成测试,而不是单元测试。单元测试专门针对测试对象编写。在您的案例代码中:
PersonController controller = new PersonController();
var result = controller.Create(someEmployerID);
不应该使用Repository
的真实实现。您需要将模拟的存储库实例注入PersonController
。我假设你没有使用任何IoC containtainers,所以为了能够将它注入你的控制器,你可以添加另一个构造函数:
private IRepository _repository;
public PersonController() : this(new Repository()) //real implementation
{}
public PersonController(IRepository repository)
{
_repository = repository;
}
// your test
var reporitory = new Mock<IRepository>();
var controller = new PersonController(repository.Object);
controller.CreateEmployee(someId);
// assert that your repository was called
repository.Verify(...);
这种技术称为Poor Man's injection,它不是推荐的注入方式,但它比仅具有具体实例更好。
接下来,如果您要为Repository
编写单元测试(不是集成),那么您需要有一个类似IDbContext
的界面将是DbContext
的包装器。并为Repository
创建2个构造函数,如PersonController
- 参数少,使用IDbContext
。
更新:忽略我关于Repository
和DbContext
的最后陈述。我已经检查了文档DbChangeTracker.Entries()
方法不是虚拟的,这意味着你将无法使用Moq
库来模拟它。您需要使用another mocking framework或使用 intergation 测试(没有模拟实例)对其进行测试。