2012-06-04 17 views
20

En RhinoMocks, puede simplemente decir sus simulaciones a IgnoreArguments como una declaración general. En Moq, parece que debes especificar It.IsAny() para cada argumento. Sin embargo, esto no funciona para los argumentos de ref y out. ¿Cómo puedo probar el siguiente método donde necesito MOq la llamada de servicio interno para devolver un resultado específico:Cómo hacer que Moq ignore los argumentos que son ref o out

public void MyMethod() { 
    // DoStuff 

    IList<SomeObject> errors = new List<SomeObject>(); 
    var result = _service.DoSomething(ref errors, ref param1, param2); 

    // Do more stuff 
} 

Método de ensayo:

public void TestOfMyMethod() { 
    // Setup 
    var moqService = new Mock<IMyService>(); 
    IList<String> errors; 
    var model = new MyModel(); 

    // This returns null, presumably becuase "errors" 
    // here does not refer to the same object as "errors" in MyMethod 
    moqService.Setup(t => t.DoSomething(ref errors, ref model, It.IsAny<SomeType>()). 
     Returns(new OtherType())); 
} 

ACTUALIZACIÓN: Por lo tanto, el cambio de los errores de "ref" a "fuera" funciona. Por lo tanto, parece que el verdadero problema es tener un parámetro de referencia que no pueda inyectar.

+0

Se puede publicar la firma del 'DoSomething' porque en su muestra Tiene 3 argumentos' errores hacia fuera, param1, param2' pero en su prueba de que está llamando con dos argumentos 'errores hacia fuera, It.IsAny () 'Tal vez te estás burlando de una sobrecarga equivocada, porque tu código debería funcionar de todos modos, consulta la [sección de métodos de ayuda moq] (http://code.google.com/p/moq/wiki/QuickStart). – nemesv

+0

Eso fue solo un ejemplo, pero he actualizado la prueba para que sea paralelo al código de ejemplo – sydneyos

Respuesta

14

Como ya descubrió el problema es con su argumento ref.

Moq actualmente solo admite coincidencias exactas para los argumentos ref, lo que significa que la llamada solo coincide si pasa la misma instancia a la que utilizó en el Setup. Por lo tanto, no hay coincidencia general, por lo que It.IsAny() no funcionará.

Ver Moq quickstart

// ref arguments 
var instance = new Bar(); 
// Only matches if the ref argument to the invocation is the same instance 
mock.Setup(foo => foo.Submit(ref instance)).Returns(true); 

y MOQ discussion group:

Ref coincidencia significa que la instalación sólo es comparable si el método es llamado con esa misma instancia. It.IsAny devuelve nulo, por lo que probablemente no es lo que estás buscando.

Utilice la misma instancia en la configuración que la de la llamada real, y la configuración coincidirá.

+1

Entonces, en el caso donde hay llamadas anidadas tales que la variable ref/out se establece dentro del método que hace la llamada que intento burlarse, estoy atascado. ¿Alguna sugerencia para un marco burlón que no tenga esta limitación? – sydneyos

+0

Creo que las opciones de RhinoMocks IgnoreArguments() deberían hacerlo, lo intentaré. – sydneyos

+0

Actualización, para estas situaciones, tuvimos que cambiar a RhinoMocks. La implementación se ve así: IList errors; _repository.Stub (t => t.MethodName (errores de salida) .OutRef (nueva lista ()). IgnoreArguments(); – sydneyos

2

Como @nemesv mencionado anteriormente, It.IsAny devuelve nulo por lo que no puede usarlo como un parámetro de referencia. Para que la llamada funcione, es necesario que se le pase un objeto real.

El problema ocurre cuando no tiene acceso a la creación del objeto que quiere pasar por ref. Si tuviste acceso al objeto real, puedes usarlo en tu prueba y olvidarte de intentar burlarte de él.

Aquí hay una solución utilizando la técnica Extraer e invalidar que le permitirá hacer precisamente eso. Como su nombre lo indica, extrae el bit de código problemático en su propio método. Luego, anula el método en una clase de prueba que hereda de la clase bajo prueba. Finalmente, configura su objeto real, lo pasa a su nueva clase de prueba y prueba sus llamadas de ref como lo desee.

Aquí hay un largo código (artificial), pero muestra el antes y el después, con una prueba de aprobación al final.

using System; 
using System.Collections.Generic; 
using Moq; 
using MoqRefProblem; 
using NUnit.Framework; 

namespace MoqRefProblem 
{ 
    //This class is the one we want to have passed by ref. 
    public class FileContext 
    { 
     public int LinesProcessed { get; set; } 
     public decimal AmountProcessed { get; set; } 
    } 

    public interface IRecordParser 
    { 
     //The ref parameter below is what's creating the testing problem. 
     void ParseLine(decimal amount, ref FileContext context); 
    } 

    //This is problematic because we don't have a 
    //seam that allows us to set the FileContext. 
    public class OriginalFileParser 
    { 
     private readonly IRecordParser _recordParser; 

     public OriginalFileParser(IRecordParser recordParser) 
     { 
      _recordParser = recordParser; 
     } 

     public void ParseFile(IEnumerable<decimal> items) 
     { 
      //This is the problem 
      var context = new FileContext(); 
      ParseItems(items, ref context); 
     } 

     private void ParseItems(IEnumerable<decimal> items, ref FileContext context) 
     { 
      foreach (var item in items) 
      { 
       _recordParser.ParseLine(item, ref context); 
      } 
     } 
    } 

    } 

    //This class has had the creation of the FileContext extracted into a virtual 
    //method. 
    public class FileParser 
    { 
     private readonly IRecordParser _recordParser; 

     public FileParser(IRecordParser recordParser) 
     { 
      _recordParser = recordParser; 
     } 

     public void ParseFile(IEnumerable<decimal> items) 
     { 
      //Instead of newing up a context, we'll get it from a virtual method 
      //that we'll override in a test class. 
      var context = GetFileContext(); 
      ParseItems(items, ref context); 
     } 

     //This is our extensibility point 
     protected virtual FileContext GetFileContext() 
     { 
      var context = new FileContext(); 
      return context; 
     } 

     private void ParseItems(IEnumerable<decimal> items, ref FileContext context) 
     { 
      foreach (var item in items) 
      { 
       _recordParser.ParseLine(item, ref context); 
      } 
     } 
    } 

    //Create a test class that inherits from the Class under Test  
    //We will set the FileContext object to the value we want to 
    //use. Then we override the GetContext call in the base class 
    //to return the fileContext object we just set up. 
    public class MakeTestableParser : FileParser 
    { 
     public MakeTestableParser(IRecordParser recordParser) 
      : base(recordParser) 
     { 
     } 

     private FileContext _context; 

     public void SetFileContext(FileContext context) 
     { 
      _context = context; 
     } 

     protected override FileContext GetFileContext() 
     { 
      if (_context == null) 
      { 
       throw new Exception("You must set the context before it can be used."); 
      } 

      return _context; 
     } 
    } 

[TestFixture] 
public class WorkingFileParserTest 
{ 
    [Test] 
    public void ThisWillWork() 
    { 
     //Arrange 
     var recordParser = new Mock<IRecordParser>(); 

     //Note that we are an instance of the TestableParser and not the original one. 
     var sut = new MakeTestableParser(recordParser.Object); 
     var context = new FileContext(); 
     sut.SetFileContext(context); 

     var items = new List<decimal>() 
      { 
       10.00m, 
       11.50m, 
       12.25m, 
       14.00m 
      }; 

     //Act 
     sut.ParseFile(items); 

     //Assert 
     recordParser.Verify(x => x.ParseLine(It.IsAny<decimal>(), ref context), Times.Exactly(items.Count)); 
    } 
} 
Cuestiones relacionadas