2014-06-17 1 views
2

최근에 시도했을 때 AutoFixture as an NSubstitute auto-mocking container을 실행했을 때 예상치 못한 적자가 발생했습니다. 인터페이스 타입으로 지정된 생성자/팩토리 매개 변수에 대한 대용 물이 자동으로 생성되는 것처럼 보일지라도, 생성 된 대용 물/모의 객체는 자동으로 구성되어있어 예상대로 조명기를 통해 지정된 자동 값을 반환하지 않습니다.프록시 인터페이스에서 자동 값을 반환하는 NSubstitute를 사용하여 AutoFixture에서 지원/지원할 수 있습니까

상자 밖으로 나 가면서 생각했던 것을 설명하기 위해 아래 간단한 테스트를 만들었습니다. 난 그냥 AutoNSubstituteCustomization 구현에 포함되지 않은 무언가를 기대하고있어 경우

[Test] 
    public void MyClass_WhenAskedToDoSomething_ShouldReturnANumberFromSomeService() 
    { 
     // Arrange 
     var fixture = new Fixture().Customize(new AutoNSubstituteCustomization()); 
     var expectedNumber = fixture.Freeze<int>(); 

     var sut = fixture.Create<MyClass>(); 

     // Act 
     var number = sut.AskToDoSomething(); 

     // Assert 
     Assert.IsTrue(number == expectedNumber); 
    } 

    public class MyClass 
    { 
     private readonly IMyInterface _myInterface; 

     public MyClass(IMyInterface myInterface) 
     { 
      _myInterface = myInterface; 
     } 

     public int AskToDoSomething() 
     { 
      return _myInterface.GetService().GetNumber(); 
     } 
    } 

    public interface IMyInterface 
    { 
     ISomeService GetService(); 
    } 

    public interface ISomeService 
    { 
     int GetNumber(); 
    } 

,이 같은 뭔가 해낼 어렵다. 다른 사람이이 방향으로 밟았습니까? 모든 포인터가 도움이됩니다.

+2

이 질문은 [이 중 하나] (http://stackoverflow.com/q/18497489/126014)와 밀접한 관련이 있습니다. 다른 질문은 NSq가 아니라 Moq에 대한 질문입니다. NSubstitute에 충분한 확장 성 포인트가 존재하는지 여부를 말할 수있을 정도로 NSubstitute를 잘 모르는 경우에도 거의 동일 할 수 있습니다. –

+0

그래서 내가 모을 수있는 것은 내가보고있는 동작이 예상되며 원래 예상했던대로 동작하도록 구성 할 수 있다는 것입니다. 지금 GitHub에서 AutoNSubstituteCustomization을 구현하고 있습니다. – jpierson

+0

AutoNSubstituteCustomization을 볼 필요가 없습니다. 그것은 당신이 원하는 것을하지 않습니다. 이 작업에 정말로 관심이 있다면 다른 라이브러리를 객체 생성 루틴에 연결할 수 있도록 확장 점이 있는지 알아보기 위해 NS substitute를 살펴 봐야합니다. –

답변

0

나는이 찌르기를 스스로 취한 이래로 나는 내가 지금까지 생각해 왔던 것을 게시해야한다고 생각했다. 다음은 개별 NS substitute 대체 인스턴스에 광범위한 기본값 기능을 적용 할 수있는 유형 집합입니다. 구성 방법은이 각 대체 인스턴스를 호출 할 필요 어느 클래스 또는 AutoNSubstitute의 여분의 구현을 지원하는 AutoFixture AutoNSubstitute에서 지원 클래스의 어떤 방해 변형 될 수 있어야하기 때문에

public interface IDefaultValueFactory 
{ 
    T GetDefault<T>(); 
} 

public static class NSubstituteDefaultValueConfigurator 
{ 
    public static void Configure(Type substituteType, object substitute, IDefaultValueFactory valueFactory) 
    { 
     var type = typeof(NSubstituteDefaultValueConfigurator<>) 
      .MakeGenericType(substituteType); 

     var configurator = type 
      .GetConstructor(new Type[] { typeof(IDefaultValueFactory) }) 
      .Invoke(new object[] { valueFactory }); 

     type.GetMethod("ConfigureDefaultReturnValuesForAllMethods") 
      .Invoke(configurator, new object[] { substitute }); 
    } 
} 


public class NSubstituteDefaultValueConfigurator<T> 
{ 
    private readonly IDefaultValueFactory _valueFactory; 

    public NSubstituteDefaultValueConfigurator(IDefaultValueFactory valueFactory) 
    { 
     _valueFactory = valueFactory; 
    } 

    private object GetDeafultValue<TResult>() 
    { 
     return _valueFactory.GetDefault<TResult>(); 
    } 


    public void ConfigureDefaultReturnValuesForAllMethods(T substitute) 
    { 
     var interfaces = substitute 
      .GetType() 
      .GetInterfaces() 
      // HACK: Specifically exclude supporting interfaces from NSubstitute 
      .Where(i => 
       i != typeof(Castle.DynamicProxy.IProxyTargetAccessor) && 
       i != typeof(ICallRouter) /*&& 
       i != typeof(ISerializable)*/) 
      .ToArray(); 

     var methods = interfaces 
      .SelectMany(i => i.GetMethods()) 
      .Where(m => m.ReturnType != typeof(void)) 

      // BUG: skipping over chained interfaces in NSubstitute seems 
      // to cause an issue with embedded returns. Using them however 
      // causes the mock at the end or along a chained call not to be 
      // configured for default values. 
      .Where(m => !m.ReturnType.IsInterface); 

     foreach (var method in methods) 
     { 
      var typedConfigureMethod = this 
       .GetType() 
       .GetMethod("ConfigureDefaultReturnValuesForMethod", BindingFlags.NonPublic | BindingFlags.Static) 
       .MakeGenericMethod(method.ReturnType); 

      var defaultValueFactory = new Func<CallInfo, object>(
       callInfo => this 
        .GetType() 
        .GetMethod("GetDeafultValue", BindingFlags.NonPublic | BindingFlags.Instance) 
        .MakeGenericMethod(method.ReturnType) 
        .Invoke(this, null)); 

      typedConfigureMethod.Invoke(
       this, 
       new object[] 
        { 
         substitute, 
         defaultValueFactory, 
         method 
        }); 
     } 

     //var properties = interfaces.SelectMany(i => i.GetProperties()); 
     var properties = substitute 
      .GetType().GetProperties(); 

     foreach (var property in properties) 
     { 
      var typedConfigureMethod = this 
       .GetType() 
       .GetMethod("ConfigureDefaultReturnValuesForProperty", BindingFlags.NonPublic | BindingFlags.Static) 
       .MakeGenericMethod(property.PropertyType); 

      var defaultValueFactory = new Func<CallInfo, object>(
       callInfo => this 
        .GetType() 
        .GetMethod("GetDeafultValue", BindingFlags.NonPublic | BindingFlags.Instance) 
        .MakeGenericMethod(property.PropertyType) 
        .Invoke(this, null)); 

      typedConfigureMethod.Invoke(
       this, 
       new object[] 
        { 
         substitute, 
         defaultValueFactory, 
         property 
        }); 
     } 
    } 

    private static void ConfigureDefaultReturnValuesForMethod<TResult>(
     T substitute, 
     Func<CallInfo, object> defaultValueFactory, 
     MethodInfo method) 
    { 
     var args = method 
      .GetParameters() 
      .Select(p => GetTypedAnyArg(p.ParameterType)) 
      .ToArray(); 

     // Call the method on the mock 
     var substituteResult = method.Invoke(substitute, args); 

     var returnsMethod = typeof(SubstituteExtensions) 
      .GetMethods(BindingFlags.Static | BindingFlags.Public) 
      .First(m => m.GetParameters().Count() == 2) 
      .MakeGenericMethod(method.ReturnType); 

     var typedDefaultValueFactory = new Func<CallInfo, TResult>(callInfo => (TResult)defaultValueFactory(callInfo)); 

     returnsMethod.Invoke(null, new[] { substituteResult, typedDefaultValueFactory }); 
    } 

    private static void ConfigureDefaultReturnValuesForProperty<TResult>(
     T substitute, 
     Func<CallInfo, object> defaultValueFactory, 
     PropertyInfo property) 
    { 
     // Call the property getter on the mock 
     var substituteResult = property.GetGetMethod().Invoke(substitute, null); 

     var returnsMethod = typeof(SubstituteExtensions) 
      .GetMethods(BindingFlags.Static | BindingFlags.Public) 
      .First(m => m.GetParameters().Count() == 2) 
      .MakeGenericMethod(property.PropertyType); 

     var typedDefaultValueFactory = new Func<CallInfo, TResult>(callInfo => (TResult)defaultValueFactory(callInfo)); 

     returnsMethod.Invoke(null, new[] { substituteResult, typedDefaultValueFactory }); 
    } 

    private static object GetTypedAnyArg(Type argType) 
    { 
     return GetStaticGenericMethod(typeof(Arg), "Any", argType); 
    } 

    private static MethodInfo GetStaticGenericMethod(
     Type classType, 
     string methodName, 
     params Type[] typeParameters) 
    { 
     var method = classType 
      .GetMethod(methodName, BindingFlags.Static | BindingFlags.Public) 
      .MakeGenericMethod(typeParameters); 

     return method; 
    } 
} 

제공해야 . AutoNSubstitute 소스 코드 내에서 직접 수정 한 결과, NSubstituteBuilder 클래스를 다음과 같이 수정하여 구성 가능한 기본/자동 값 기능을 갖도록 조정했습니다.

public object Create(object request, ISpecimenContext context) 
    { 
     if (!SubstitutionSpecification.IsSatisfiedBy(request)) 
      return new NoSpecimen(request); 

     var substitute = Builder.Create(request, context); 
     if (substitute == null) 
      return new NoSpecimen(request); 

     NSubstituteDefaultValueConfigurator.Configure(
      substitute.GetType(), 
      substitute, 
      new AutoFixtureDefaultValueFactory(context)); 

     return substitute; 
    } 

    private class AutoFixtureDefaultValueFactory : IDefaultValueFactory 
    { 
     private readonly ISpecimenContext _context; 

     public AutoFixtureDefaultValueFactory(ISpecimenContext context) 
     { 
      _context = context; 
     } 

     public T GetDefault<T>() 
     { 
      return _context.Create<T>(); 
     } 
    } 

불행하게도 하나 반사를 처리하는 내 구현에 버그가 대체에 부동산 취득 메소드 호출 또는 NSubstitute이 방법보다 속성을 처리하는 차이가 있지만 나는 장애물 약간의 어느 쪽이든으로 실행했습니다있다. 나머지 문제는 리프 멤버 호출에서 AutoFixture를 통해 확인해야하는 구체적인 클래스가 발견 될 때 CouldNotSetReturnException이 throw된다는 체인 된 인터페이스 (멤버에서 다른 인터페이스를 반환하는 인터페이스) 때문입니다. 이것은 흥미롭고 불행한 방법이지만 속성이 아닌 메서드에서만 발생하는 것으로 보입니다. NSubsitute Returns method design의 제한 사항과 configuring default values more broadly에 대한 일반 API의 제한 사항을 감안할 때

이 시점에서 대답은 '아니오'입니다. AutoFixture의 AutoNSubstitute 사용자 지정 기능은 반환 된 대체 멤버를 통해 조명기에서 반환 된 동일한 자동 값을 반환하는 기능을 지원하지 않습니다. 다른 한편, AutoFixture의 관리자는이 기능의 합리적인 구현을 수락하고 아마도 지원할 것으로 보이며, NS substitute의 사용 가능한 기능을 사용하여 적어도 부분적으로 작동하는 구현을 달성 할 수 있음을 보여줄 수있었습니다. 가감.

부수적으로, 정적 팩토리를 사용하여 모의 객체를 만드는 모의 라이브러리는 인스턴스 기반의 컨텍스트 유형을 갖지 않는 자연스럽게 생성 된 모의 객체의 동작을 구성 할 수 없다는 점이 분명합니다. 테스트 당. 나는 단위 테스트에서 처음으로 모의를 채택 할 때이 한계에 대해 일찍 생각했고 이것이 나에게 문제를 일으키는 것으로 보이는 것은 처음이다.

관련 문제