关于c#:自动化测试中的DbContext.ChangeTracker会抛出SQLException

DbContext.ChangeTracker in automated tests throws SQLException

我正在为一个我一直在努力的项目编写自动化测试,以便更熟悉MVC、EntityFramework(代码优先)、单元测试和MOQ。

我在我的Repository类中有一个部分,当控制器调用Repository.SaveChanges()时,它设置我的模型的LastModified字段(mymodelbase是一个基类):

1
2
3
4
5
6
7
8
9
10
11
12
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中的dbset并设置测试数据。

这是我奇怪的地方:我的单元测试运行良好(通过),但它们从未真正进入过foreach循环——当访问ChangeTracker.Entities()并退出循环时,它挂起,跳到_dbContext.SaveChanges()。没有错误。

但是,在一个与我共享项目的朋友的机器上,当访问ChangeTracker.Entities()时,他会得到一个sqlException。我确实在VS2015中检查了抛出的sqlExceptions,并且在我这方面没有输出或其他异常指示。

Result StackTrace:
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
....

Result Message:
Test method MyProject.Tests.TestClasses.MyControllerTests.TestCreate threw exception:
System.Data.SqlClient.SqlException: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

最后,我的问题是:有没有一种方法可以使用moq来模拟changetracker(我怀疑不是从先前的调查中得出的),或者有没有另一种方法可以使用我的RepoSaveChanges()来自动设置属性?如果不访问ChangeTracker.Entities(),我将需要更新逻辑来为每个具有该字段的模型类型设置LastModified字段。同样,我想避免使用该API/框架的一部分,因为测试是顽固的并不理想。

对于我的机器上为什么没有抛出/不能捕获sqlException,有人有什么见解吗?或者对如何在单元测试中使用ChangeTracker.Entities()有什么建议?我只会在我所有的模型和控制器上单独设置LastModified属性作为最后的手段。

更新:已经请求了更多的示例代码,所以让我进一步了解细节。我使用moq模拟dbContext,然后模拟dbContext中包含的dbset对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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


我得出了一个不太理想的解决方案,但足以让我继续前进。我绕过了DbContext.ChangeTracker.Entries()API,只是在类中添加了一个抽象级别,扩展了DbContext称为MyApplicationDbContext的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()来迭代problem语句中描述的业务逻辑的entries()。但是,由于我将该房地产设定为virtual,因此我可以使用MoQ设置回报:

1
2
3
List<SomeModel> addedEntries = new List<SomeModel>();
addedEntries.add(someModelWhichWillBeAddedByTheController);
mockContext.Setup(context => context.AddedEntries).Returns(addedEntries);

这样,当使用EDCOX1×19属性时,控制器将观察EDCOX1×18Ω。缺点是,我无法测试实际业务逻辑中使用的EDCOX1 12码,但稍后我将通过实现与测试数据库的集成测试来实现这一点。

我一直找不到EDCOX1 21被抛出一台机器而不是另一台机器的原因。


例外情况是它无法创建到数据库的连接,我怀疑你和你的朋友在app.config文件中有不同的连接字符串,或者你的朋友没有访问数据库的权限。

实际上,您正在编写一个集成测试,而不是单元测试。单元测试是专门为测试对象编写的。在您的案例代码中:

1
2
PersonController controller = new PersonController();
var result = controller.Create(someEmployerID);

不应使用实际的EDOCX1实现〔0〕。您需要将存储库的模拟实例注入到PersonController中。我假设您没有使用任何IOC集装箱,因此为了能够将其注入控制器,您可以添加另一个构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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(...);

这项技术被称为穷人的注射,它不是推荐的注射方式,但它比只有具体的实例更好。

接下来,如果您想为您的Repository编写单元测试(而不是集成),那么您需要一个像IDbContext这样的接口,它将是您的DbContext的包装器。并为您的Repository创建2个构造器,就像在PersonController中那样—无参数,使用IDbContext创建。

更新:忽略我上次关于RepositoryDbContext的声明。我检查过文档DbChangeTracker.Entries()方法不是虚拟的,这意味着您将无法使用Moq库模拟它。您需要使用另一个模拟框架,或者使用集成测试(没有模拟实例)测试它。


您的错误是它无法创建到数据库的连接。这是在创建dbContext时发生的,在该上下文中,ef将开始执行诸如检查是否需要迁移数据库之类的操作。

在我看来,您还需要模拟dbContext的构造函数。我不太熟悉moq本身,但是如果我正确地阅读了您的代码,我看不到您在嘲笑构造函数。