2013-05-24 2 views
3

Я прочитал Mark Seeman's article on auto-mocking, и теперь я пишу повторно используемый контейнер для виндсерфинга на основе этой статьи.Automocking the SUT

Моя реализация статье Марка (в основном скопированы непосредственно)

Основная работа выполняется в AutoMoqResolver классе. Это позволит издеваться каждый раз, когда класс имеет зависимость от интерфейса:

public class AutoMoqResolver : ISubDependencyResolver 
{ 
    private readonly IKernel kernel; 

    public AutoMoqResolver(IKernel kernel) 
    { 
     this.kernel = kernel; 
    } 

    public bool CanResolve(
     CreationContext context, 
     ISubDependencyResolver contextHandlerResolver, 
     ComponentModel model, 
     DependencyModel dependency) 
    { 
     return dependency.TargetType.IsInterface; 
    } 

    public object Resolve(
     CreationContext context, 
     ISubDependencyResolver contextHandlerResolver, 
     ComponentModel model, 
     DependencyModel dependency) 
    { 
     var mockType = typeof(Mock<>).MakeGenericType(dependency.TargetType); 
     return ((Mock)this.kernel.Resolve(mockType)).Object; 
    } 
} 

AutoMoqResolver добавляется в контейнер, используя следующую реализацию интерфейса IWindsorInstaller:

public class AutoMockInstaller<T> : IWindsorInstaller 
{ 
    public void Install(
     IWindsorContainer container, 
     IConfigurationStore store) 
    { 
     container.Kernel.Resolver.AddSubResolver(
      new AutoMoqResolver(container.Kernel)); 

     container.Register(Component.For(typeof(Mock<>))); 

     container.Register(Classes 
      .FromAssemblyContaining<T>() 
      .Pick() 
      .WithServiceSelf() 
      .LifestyleTransient()); 
    } 
} 

Тогда мой контейнер просто работает установщик и он готов автоматически предоставлять mocks для любых зависимостей интерфейса в модульных тестах:

public class AutoMockContainer<T> : WindsorContainer 
{ 
    public AutoMockContainer() 
    { 
     // simply run the auto-mock installer 
     this.Install(new AutoMockInstaller<T>()); 
    } 
} 

Супер!

Я протестировал это, и мои зависимости счастливо издеваются над собой, и я затем применил его к какому-то реальному коду. Это когда я понял, что решение не помогает мне из-за шаблона, который я обычно наблюдаю при тестировании класса. Моя особая проблема заключается в том, что я хочу иметь возможность самофиксации самого SUT, чтобы проверить, что один метод на SUT вызывается из другого.

Мой код, который должен быть протестирован

Я объясню себя в качестве примера. Я разрабатываю MVC код и я поддерживаю ненавязчивый AJAX с помощью следующей общей схеме:

public Class ExampleController : Controller 
{ 
    private IService service; 

    public ExampleController(IService service) 
    { 
     this.service = service; 
    } 

    public PartialViewResult DoSomethingWithAjax() 
    { 
     this.PerformTask(); 

     return this.PartialView(); 
    } 

    public RedirectToRouteResult DoSomethingWithoutAjax() 
    { 
     this.PerformTask(); 

     return this.RedirectToAction("SomeAction"); 
    } 

    protected virtual void PerformTask() 
    { 
     // do something here 
    } 
} 

Мой тестовый образец

Так что для того, чтобы убедиться в том, что метод PerformTask() был вызван из DoSomethingWithAjax() или DoSomethingWithoutAjax(), я определить новый класс TestableExampleController так:

public class TestableExampleController : ExampleController 
{ 
    public TestableExampleController(IService service) : base(service) 
    { 
    } 

    public virtual void PerfomTaskPublic() 
    { 
     base.PerfomTask(); 
    } 

    protected override void PerformTask() 
    { 
     this.PerformTaskPublic(); 
    } 
} 

можно затем использовать TestableExampleController как мой SUT так следующим TES т будет проходить:

[TestMethod] 
public void DoSomethingAjax_Calls_PerformTask() 
{ 
    //// Arrange 
    // create a mock TestableExampleController 
    var controllerMock = new Mock<TestableExampleController>(); 
    controllerMock.CallBase = true; 

    // use the mock controller as the SUT 
    var sut = controllerMock.Object; 

    //// Act 
    sut.DoSomethingAjax(); 

    //// Assert 
    controllerMock.Verify(x => x.PerformTaskPublic(), Times.Once()); 
} 

Моя проблема

Рефакторинг этот тест, чтобы использовать мой AutoMockContainer класс, как это не работает:

[TestMethod] 
public void DoSomethingAjax_Calls_PerformTask() 
{ 
    //// Arrange 
    // create a container 
    var container = new AutoMockContainer<TestableExampleController>(); 

    // resolve a mock SUT using the container 
    var controllerMock = container.Resolve<Mock<TestableExampleController>>(); 
    controllerMock .CallBase = true; 

    // use the mock controller as the SUT 
    var sut = controllerMock.Object; 

    //// Act 
    sut.DoSomethingAjax(); 

    //// Assert 
    controllerMock.Verify(x => x.PerformTaskPublic(), Times.Once()); 
} 

тест не удается создать экземпляр Mock<TestableExampleController> потому что он не может найти конструктор без параметров.

Невозможно создать прокси-класс класса: MyNamespace.TestableExampleController. Не удалось найти конструктор без параметров.Имя Параметр: constructorArguments

Мой Предложенное решение

В идеале я хотел бы реализовать класс-оболочку, которая может быть зарегистрирована с контейнером для автоматического предоставления издеваться для любого компонента:

public class ComponentWrapper<T> where T : class 
{ 
    public ComponentWrapper(Mock<T> componentMock) 
    { 
     componentMock.CallBase = true; 
     this.ComponentMock = componentMock; 
    } 

    public Mock<T> ComponentMock { get; private set; } 

    public T Component 
    { 
     get { return this.ComponentMock.Object; } 
    } 
} 

Я бы хотел (могли) написать следующее тестирование:

[TestMethod] 
public void DoSomethingAjax_Calls_PerformTask() 
{ 
    //// Arrange 
    // create a container 
    var container = new AutoMockContainer<TestableExampleController>(); 

    // resolve a ComponentWrapper using the container 
    var wrapper = container.Resolve<ComponentWrapper<TestableExampleController>>(); 

    //// Act 
    // call a method using the component 
    wrapper.Component.DoSomethingAjax(); 

    //// Assert 
    // verify a method call using the mock 
    wrapper.ComponentMock.Verify(x => x.PerformTaskPublic(), Times.Once()); 
} 

Я не могу понять, как это достичь, и большую часть дня я провел с новыми реализациями ISubDependencyResolver, но я просто не могу заставить это работать.

Надеюсь, мой вопрос ясен, и ответ на самом деле относительно прост?

+2

Это может помочь: http://stackoverflow.com/questions/11958110/technique-for-using-autofixture-to-integration-test-an-application -using-castle Если вы в порядке с использованием AutoFixture, вы можете добавить тег автоистории. – TrueWill

+0

Привет, Ник, я сделал что-то подобное для использования с Rhino Mocks. У меня нет кода здесь, но я помню, что использовал Lazy Component Loader. Таким образом, вы можете зарегистрировать любую зависимость после ее запроса. Таким образом, вы можете увидеть, будет ли это работать вместо вас, а не с помощью подзависимости. – Marwijn

ответ

5

Оказалось, что AutoFixture.AutoMoq сделает именно то, что я хочу из коробки, поэтому спасибо TrueWill за то, что указал мне в правильном направлении.

Следующий простой тест будет проходить:

[TestMethod] 
public void Run_Calls_DoSomethingProtected() 
{ 
    //// Arrange 
    // AutoMoqCustomization allows AutoFixture to 
    // be used an an auto-mocking container 
    var fixture = new Fixture().Customize(new AutoMoqCustomization()); 

    // simply ask the fixture to create a mock 
    var sutMock = fixture.Create<Mock<TestableDummySystem>>(); 

    //// Act 
    // exercise the mock object 
    sutMock.Object.Run(); 

    //// Assert 
    // this verification passes! 
    sutMock.Verify(x => x.DoSomethingProtectedPublic()); 
} 
Смежные вопросы