2015-09-20 4 views
1

У меня проблемы с тестированием моего приложения, используя Owin.TestServer. Я не мог найти ничего полезного, и я надеюсь, что это легко исправить, что сообщество может помочь с:)WebApi2 с OWIN TestServer и AutoFac - LifetimeScope уже установлен

Недавно я начал писать интеграционные тесты для моего приложения WebApi, использующего OWIN и AutoFac для DI. У меня всего 3 интеграционных теста. Когда я запускаю каждый тест по отдельности, все они проходят. Однако, когда я бегу все тесты сразу, только первый один преуспевает и другие два потерпели неудачу из-за следующей ошибки AutoFac:

System.AggregateException: One or more errors occurred. ---> 
System.ObjectDisposedException: Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed. 

StackTrace указывает, что ошибка происходит из промежуточного AutoFac для Owin.

У меня после установки для испытаний:

[TestClass] 
public class DinnerListControllerTests 
{ 
    private TestServer _server; 
    private TransactionScope _transactionScope; 

    [TestInitialize] 
    public void Init() 
    { 
     _transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew); 
     _server = TestServer.Create<Startup>(); 
    } 

    [TestCleanup] 
    public void Dispose() 
    { 
     _server?.Dispose(); 
     _transactionScope?.Dispose(); 
    } 

    [TestMethod] 
    public void GetAllLists() 
    { 
     var response = _server.HttpClient.GetAsync("/api/dinnerlists").Result; 
     response.IsSuccessStatusCode.Should().BeTrue("there should be no error"); 
     var result = response.Content.ReadAsAsync<IEnumerable<DinnerListDTO>>().Result; 
     result.Should().NotBeNull().And.HaveCount(5); 
    } 

    [TestMethod] 
    public void GetActiveListsReturnsTwoLists() 
    { 
     var response = _server.HttpClient.GetAsync("/api/dinnerlists/active").Result; 
     response.IsSuccessStatusCode.Should().BeTrue(); 

     var result = response.Content.ReadAsAsync<IEnumerable<DinnerListDTO>>().Result; 
     result.Should() 
      .NotBeNullOrEmpty() 
      .And.HaveCount(2) 
      .And.OnlyContain(dto => dto.OpenUntil.CompareTo(DateTime.Now) > 0); 
    } 
} 

GetAllLists тест будет выполняться правильно, но второй один потерпит неудачу с сообщением указанного выше.

Я пробовал с различными регистрационными областями зависимостей, но это не помогло. Ниже моя конфигурация AutoFac, класс запуска и образец AutoFac модуль:

AutoFacConfig.cs

public class AutoFacConfig 
{ 
    private static IContainer _container; 
    public static IContainer Container => _container ?? (_container = BuildContainer()); 

    public static void ConfigureAutoFac(HttpConfiguration config) 
    { 
     if (config == null) 
      throw new ArgumentNullException(nameof(config)); 

     FluentValidationModelValidatorProvider.Configure(config, 
      provider => provider.ValidatorFactory = new AutoFacValidatorFactory(Container)); 

     config.DependencyResolver = new AutofacWebApiDependencyResolver(Container); 
    } 

    private static IContainer BuildContainer() 
    { 
     var autoFacBuilder = new ContainerBuilder(); 
     var assembly = Assembly.GetExecutingAssembly(); 
     autoFacBuilder.RegisterApiControllers(assembly).InstancePerRequest(); 

     autoFacBuilder.RegisterType<DinnerDbContext>().InstancePerRequest(); 
     autoFacBuilder.RegisterModule<RepositoryModule>(); 
     autoFacBuilder.RegisterModule<ServicesModule>(); 
     autoFacBuilder.RegisterModule<ValidationModule>(); 
     autoFacBuilder.RegisterModule<AutoMapperModule>(); 
     autoFacBuilder.RegisterModule<AutofacWebTypesModule>(); 

     return autoFacBuilder.Build(); 
    } 
} 

Startup.cs:

public class Startup 
{ 
    public void Configuration(IAppBuilder appBuilder) 
    { 
     var httpConfiguration = new HttpConfiguration(); 

     WebApiConfig.Register(httpConfiguration); 
     FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 

     AutoFacConfig.ConfigureAutoFac(httpConfiguration); 
     AutoMapperConfig.RegisterMappings(); 

     appBuilder.UseAutofacMiddleware(AutoFacConfig.Container); 
     appBuilder.UseAutofacWebApi(httpConfiguration); 
     appBuilder.UseWebApi(httpConfiguration); 
    } 
} 

Пример модуля AutoFac:

public class RepositoryModule : Module 
{ 
    protected override void Load(ContainerBuilder builder) 
    { 
     var assembly = System.Reflection.Assembly.GetExecutingAssembly(); 
     builder.RegisterAssemblyTypes(assembly) 
      .Where(type => type.Name.EndsWith("Repository")) 
      .AsImplementedInterfaces() 
      .InstancePerRequest(); 
    } 
} 

EDIT - РЕШЕНИЕ

Что @Eris предложил сделал много смысла - мой AutoFacConfig класса с использованием статических методов и членов, а это означало, что Container свойства присутствовало на последующих тестах и ​​не создаются вновь, и это было помечены как расположенные.

Я решил реорганизовать код, поэтому AutoFacConfig больше не использует статические элементы, так как я не хотел играть с удалением контейнера при завершении работы приложения.

AutoFacConfig.cs:

public class AutoFacConfig 
{ 
    private IContainer _container; 
    public IContainer Container 
    { 
     get { return _container ?? (_container = BuildContainer()); } 
    } 

    public void ConfigureAutoFac(HttpConfiguration config) 
    { 
     //... 
    } 

    private IContainer BuildContainer() 
    { 
     var autoFacBuilder = new ContainerBuilder(); 
     //... 
     return autoFacBuilder.Build(); 
    } 
} 

Startup.cs

public class Startup 
{ 
    public void Configuration(IAppBuilder appBuilder) 
    { 
     var httpConfiguration = new HttpConfiguration(); 
     var autofacConfig = new AutoFacConfig(); // create instance of AutoFacConfig   
     autofacConfig.ConfigureAutoFac(httpConfiguration); // configure autofac 

     appBuilder.UseAutofacMiddleware(autofacConfig.Container); 
     appBuilder.UseAutofacWebApi(httpConfiguration); 
     appBuilder.UseWebApi(httpConfiguration); 
    } 
} 
+0

Если вы вызываете два метода в одном, будет вы получаете ошибку? –

+0

есть ли конкретная причина, по которой вы воссоздаете '_server' и' _transactionscope' для каждого теста, в отличие от одного сервера на каждое тестовое оборудование, сборку и т. Д.? – Eris

+0

@ Эрис. Я думал, что это так, особенно с областью транзакций, чтобы поддерживать соответствие данных между тестами, поскольку некоторые из них управляют базой данных. – jjczopek

ответ

2

Я подозреваю, что это AutoFacConfig вызывает проблему:

public static IContainer Container => _container ?? (_container = BuildContainer()); 

В этом случае _container не является нулевым , но в состоянии «Disposed». Он должен работать, если вы безоговорочно воссоздаете его.(Я не знаком с C# 6 синтаксис еще, так что это может быть не совсем корректно)

public static IContainer Container => _container = BuildContainer(); 

Альтернативный ответ: In self-hosted OWIN Web API, how to run code at shutdown?

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
     var context = new OwinContext(app.Properties); 
     var token = context.Get<CancellationToken>("host.OnAppDisposing"); 
     if (token != CancellationToken.None) 
     { 
      token.Register(() => 
      { 
       // code to run 
       // null out disposable resources 
      }); 
     } 
    } 
} 
+0

Я думаю, что это может быть так, проверит. Однако я бы заподозрил, что утилизация тестового терминала будет уничтожать все приложение с контейнером, но это эффектно одноэлементный, что может быть так. Я вернусь к результатам после рефакторинга. – jjczopek

+1

Да, избавление от статических предметов - это сложный бизнес, и внутренности находятся за пределами моего знания. – Eris

+0

Работал как шарм - я обновлю вопрос с решением – jjczopek