MSTest,EF6和Simple Injector - 无法在Integration Test中的对象中插入重复的键行

时间:2015-12-17 19:32:45

标签: entity-framework entity-framework-6 simple-injector

在应用程序ASPNET WebApi中,我们使用SimpleInjector作为IOC和EF6 Code First,问题是对对象进行更新,EF没有意识到它是相同的并尝试再次创建它。

从浏览器运行它时运行正常,但是当我们从集成测试中运行它时,需要定义混合上下文,因为我们在IoC中使用的LifeStyle是请求,测试是Scope。

这就是我们定义混合环境的方式。

Container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
      () => Container.GetCurrentExecutionContextScope() != null,
      new ExecutionContextScopeLifestyle(),
      new WebApiRequestLifestyle());

IocConfig.RegisterIoc(new HttpConfiguration(), Container);

然后我们必须运行[AssemblyInitialize]中定义的测试,该测试负责在数据库中创建初始测试数据。

对于每个单独的测试

[TestMethod, TestCategory("Nightly")][Ignore]
public void SearchLogsOrdersCorrectlyTest()
{
    using (GlobalInit.Container.BeginExecutionContextScope())
    {
        var subscriptionService = GlobalInit.Container.GetInstance<ISubscriptionService>();
        var subscriptionBaseModel = new SubscriptionBaseModel
            {
              AuditInformation = new AuditModel
               {
                 UserName = "User Name",
                 UserEmail = "username@domain.com",
                 UserHostname = "manage.domain.com",
                 UserIpAddress = "11.11.11.11"
               },
                 Company = new Company { Id = 1 },
                 Customers = new List<Customer> 
                   {
                     new Customer 
                       { 
                         Email = "email@dom.com", 
                         FullName = "Full Name" 
                       }
                   },
                 SubscriptionData = new SubscriptionPlanUpdateModel
                   {
                     AvailableLicenses = 1,
                     ContractId = "1111",
                     Url = "http://google.com ",
                     SubscriptionComments = ".",
                     ExpirationDate = DateTime.Today.AddYears(1)
                   }
             };

        var subscriptionCreationModel = mappingEngine.Map<SubscriptionCreationModel>(subscriptionBaseModel);
        subscriptionCreationModel.Validate();
        Assert.IsTrue(subscriptionCreationModel.IsModelValid);

        var newSubscription = SubscriptionFactory.GetSubscription(subscriptionCreationModel);

        var savedSubscription = newSubscription.Save();
        Assert.IsNotNull(savedSubscription);
    }
}

基本上,测试的作用是使用基本数据模型,从数据库中添加数据然后保存。问题是它试图将对象作为新对象插入数据库而不是更新它们。这似乎是因为它理解它们来自不同的背景。

如果我们不使用GlobalInit.Container.BeginExecutionContextScope(),那么我们会得到一个例外情况,即范围不同,并且无法在网络环境之外启动WebRequestScope。

这里的特点是所有这一切都在以下内容中完成:

using (GlobalInit.Container.BeginExecutionContextScope())

因此,如果你知道我们在这里失败了,那将会很受欢迎。

1 个答案:

答案 0 :(得分:1)

您根本不需要定义混合生活方式。这只是为每个应用程序提供自己特定的生活方式。

// In the unit tests:
var container = new Container();
container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
IocConfig.RegisterIoc(new HttpConfiguration(), container);


// In web api:
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestScope();
IocConfig.RegisterIoc(new HttpConfiguration(), container);

如果可以,请通过处理范围来防止污染每个测试。你应该做的是将范围的开始和停止移动到基类到构造函数和Dispose方法。这样它将在每次测试之前和之后运行。例如:

public abstract class IntegrationTestBase : IDisposable
{
    private Scope scope;
    public IntegrationTestBase() {
        this.scope = Container.BeginExecutionContextScope();
    }

    public void Dispose() {
        this.scope.Dispose();
    }
}

这个btw,也是添加交易处理的好地方:

public abstract class IntegrationTestBase : IDisposable
{
    private Scope scope;
    private TransactionScope transactionScope;
    public IntegrationTestBase() {
        this.scope = Container.BeginExecutionContextScope();
        this.transactionScope = new TransactionScope();
    }

    public void Dispose() {
        this.transactionScope.Dispose();
        this.scope.Dispose();
    }
}

这确保了每个测试都是孤立运行的,因为我们不调用Complete,所以在每次测试结束时回滚事务。您唯一需要做的就是从这个基类继承您的测试类。

关于你为什么有多个范围的问题:我不知道;你没有提供足够的信息来看到这一点。您可能在执行该测试期间创建了第二个范围。