c# - valid - La expresión hace referencia a un método que no pertenece al objeto burlado
summary c# (2)
Tengo un servicio de API que llama a otro servicio de API. Cuando configuré los objetos de Mock, falló con un error:
NotSupportedException: expresión hace referencia a un método que no pertenece al objeto burlado.
Este es el código:
private Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>> _mockCarrierService;
private Mock<IApiService<AccountSearchModel>> _mockApiService;
[SetUp]
public void SetUp()
{
_mockApiService = new Mock<IApiService<AccountSearchModel>>();
_mockCarrierService = new Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>>();
_mockApiService.Setup(x => x.GetFromApiWithQuery(It.IsAny<string>())).Returns(ApiValue());
// Error occurred when call _mockApiService.GetFromApiWithQuery() in .Select()
_mockCarrierService.Setup(x => x
.Select(s => s
.GetFromApiWithQuery(It.IsAny<string>())).ToList())
.Returns(new List<IQueryable<AccountSearchModel>> { ApiValue() });
}
Leí Expression testing con Moq pero no funcionó en mi caso. Si _mockCarrierService.Setup()
este _mockCarrierService.Setup()
, el caso de prueba se puede ejecutar pero falla con una NullReferenceException
porque no tenía una configuración válida de List<IQueryable<AccountSearchModel>>
.
¿Alguna idea de cómo puedo lograr esto?
Nota al pie: solución actual
FWIW, aquí está la solución que uso actualmente. Soy todo oídos para un mejor acercamiento al problema (hasta que Moq comience a apoyar métodos de extensión burlones).
private List<ICarrierApiService<AccountSearchModel>> _mockCarrierService;
private AccountSearchController _mockController;
private Mock<ICarrierApiService<AccountSearchModel>> _mockApiService;
[SetUp]
public void SetUp()
{
_mockApiService = new Mock<ICarrierApiService<AccountSearchModel>>();
_carrierServiceMocks = new List<ICarrierApiService<AccountSearchModel>> { _mockApiService.Object };
_mockApiService.Setup(x => x.GetFromApiWithQuery(It.IsAny<string>())).Returns(ApiValue());
_mockController = new AccountSearchController(_carrierServiceMocks);
}
Nota al pie: marco de burla alternativo
También encontré un marco de burla comercial que admite el método de extensión burlón y el enlace a los documentos prácticos : Telerik JustMock .
Este problema se produce porque está intentando simular el método Select
, que es un método de extensión , no un método de instancia de IEnumerable<T>
.
Básicamente, no hay forma de burlarse de un método de extensión. Eche un vistazo a esta pregunta para encontrar algunas ideas que pueden serle útiles.
UPD (12/11/2014):
Para obtener más información sobre métodos de extensión burlones, piense en lo siguiente:
Aunque los métodos de extensión se llaman como si fueran métodos de instancia en el tipo extendido, en realidad son solo métodos estáticos con un poco de azúcar sintáctico.
Los métodos de extensión del espacio de nombres
System.Linq
se implementan como funciones puras : son deterministas y no tienen ningún efecto secundario observable. Estoy de acuerdo en que los métodos estáticos son malvados, excepto aquellos que son funciones puras ; espero que también estés de acuerdo con esta afirmación :)Entonces, dado un objeto de tipo
T
, ¿cómo implementaría la función estática puraf(T obj)
? Solo es posible combinando otras funciones puras que se definen para el objetoT
(o cualquier otra función pura, en realidad), o leyendo el estado global inmutable y determinista (para mantener la funciónf
determinista y libre de efectos secundarios). En realidad, el "estado global inmutable y determinista" tiene un nombre más conveniente: una constante.
Entonces, si sigues la regla de que los métodos estáticos deben ser funciones puras (y parece que Microsoft sigue esta regla, al menos para los métodos LINQ), burlarse de un método de extensión f(this T obj)
debería poder reducirse a burlarse de los métodos no estáticos o el estado utilizado por ese método de extensión , simplemente porque ese método de extensión se basa en los métodos de instancia obj
y el estado en su implementación (y posiblemente en las otras funciones puras y / o valores constantes).
En el caso del IEnumerable<T>
de IEnumerable<T>
, Select()
se implemented en términos de declaración foreach
que, a su vez, usa el método GetEnumerator()
. De modo que puede simular GetEnumerator()
y lograr el comportamiento requerido para los métodos de extensión que dependen de él.
Tienes:
_mockCarrierService = new Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>>();
Entonces te IEnumerable<>
. El único miembro que tiene IEnumerable<>
es un método GetEnumerator()
(más otro método con la misma firma GetEnumerator()
heredada de la interfaz base). El método Select
es realmente un método de extensión (como se señaló en la primera respuesta), que es un método estático que funciona llamando a GetEnumerator()
(posiblemente a través de C # foreach
statement).
Es posible hacer que las cosas funcionen haciendo Setup
de GetEnumerator
en tu simulacro.
Sin embargo, es mucho más simple simplemente usar un tipo concreto, no simulado que "es" IEnumerable<>
, como List<>
. Así que prueba:
_mockCarrierService = new List<ICarrierApiService<AccountSearchModel>>();
Luego agregue una entrada a la List<>
. Lo que debe agregar es un Mock<ICarrierApiService<AccountSearchModel>>
en el que se configura el método GetFromApiWithQuery
.