2010-08-21 12 views
24

Estamos utilizando Moq para probar nuestras clases de servicio, pero estamos atascados en cómo probar situaciones donde un método de servicio llama a otro método de servicio de la misma clase. Intenté configurar el método que se llama a virtual, pero todavía no podía averiguar qué hacer en Moq. Por ejemplo:Usando Moq para anular los métodos virtuales en la misma clase

public class RenewalService : IRenewalService 
{ 
    //we've already tested this 
    public virtual DateTime? GetNextRenewalDate(Guid clientId) 
    { 
     DateTime? nextRenewalDate = null; 
     //...<snip> a ton of already tested stuff... 

     return nextRenewalDate; 
    } 

    //but want to test this without needing to mock all 
    //the methods called in the GetNextRenewalDate method 
    public bool IsLastRenewalOfYear(Renewal renewal) 
    { 
     DateTime? nextRenewalDate = GetNextRenewalDate(renewal.Client.Id); 
     if (nextRenewalDate == null) 
      throw new Exceptions.DataIntegrityException("No scheduled renewal date, cannot determine if last renewal of year"); 
     if (nextRenewalDate.Value.Year != renewal.RenewDate.Year) 
      return true; 
     return false; 
    } 
} 

En el ejemplo anterior, nuestro método GetNextRenewalDate es bastante complicado, y ya hemos probado que la unidad. Sin embargo, queremos probar el IsLastRenewalOfYear más simple sin necesidad de burlar todo lo necesario para GetNextRenewalDate. Básicamente, solo queremos simular GetNextRenewalDate.

Me doy cuenta de que podría crear una clase nueva que anule GetNextRenewalDate y probar la nueva clase, pero ¿hay alguna manera de aprovechar Moq para simplificar?

Respuesta

41

Puede utilizar probablemente burla parcial en este escenario, a pesar de todos sus métodos tendrían que ser virtual:

var mock = new Moq.Mock<RenewalService>(); 
    mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null); 
    mock.CallBase = true; 
    var results = mock.Object.IsLastRenewalOfYear(...); 
+0

Esto parece funcionar ... Ni siquiera necesito hacer que todo sea virtual (solo lo mantuve igual que el anterior). – Andrew

+3

@Andrew. Lo anterior funciona porque 'Mock.CallBase == true' significa que las invocaciones que no coinciden con una configuración llamarán a la implementación subyacente. Por lo tanto, 'IsLastRenewalOfYear' llamará a la implementación, porque no es virtual, pero' GetNextRenewalDate' devolverá 'null' porque la configuración siempre coincidirá. El siguiente código funcionará incluso si 'IsLastRenewalOfYear' ** es ** virtual. –

+1

usamos esto en un par de otros lugares también, funcionó como un encanto. ¡Gracias! – Andrew

0

Editar: Estoy de acuerdo en que esta no es la respuesta correcta para el caso de Andrew. Quiero dejar esta respuesta aquí para el hilo de comentarios. Por favor, no hacia abajo votan más :)

Antes de edición:

marcos de objetos simulados general no están diseñados para simplificar el escenario de una sola clase, que están diseñados para aislar su código para que pueda probar una sola clase.

Si intenta utilizar un marco de objeto simulado para resolver esto, el marco simplemente creará una clase derivada y sobrecargará ese método. Lo único diferente será que puede hacer eso en aproximadamente 3 líneas en lugar de 5, porque no tendrá que crear la definición de clase derivada.

Si desea utilizar objetos simulados para aislar este comportamiento, debe dividir esta clase un poco. La lógica GetNextRenewalDate podría vivir fuera del objeto RenewalService.

El hecho de que se encuentre con este problema puede mostrar que hay un diseño más simple o más fino que aún no se ha descubierto. Encontrar una clase que sea un poco menos concreta, con un nombre como "gerente" o "servicio" suele ser un indicio de que podría dividir el diseño en clases más pequeñas y obtener una mejor reutilización y capacidad de mantenimiento.

+0

No estoy seguro de si romper nuestro diseño haría más fácil de mantener .. .si pasamos de 5,000 clases (aproximadamente lo que tenemos actualmente) a 10,000 clases, difícilmente sería más fácil navegar. – Andrew

+0

dividir el diseño simplemente para hacerlo más comprobable dentro de un conjunto de herramientas específico parece una mala idea. Al seguir este camino, rara vez tendrías métodos para llamar a otros métodos públicos dentro de una clase. ¿Cómo es ese buen diseño? –

+0

@Andrew, Jess: No conozco su aplicación o sus requisitos, así que siéntete libre de tomar o dejar mi consejo. Sí, dividirlo solo para tener clases de método único es tonto, pero el resto de mi consejo aún se mantiene. A menudo, cuando he visto una clase de "gerente" o "servicio", las abstracciones no están en los lugares correctos. Si se hiciera un refactor de este tipo, los métodos de otras clases podrían terminar aquí, o estos métodos podrían terminar en otras clases, haciendo que esta clase desaparezca por completo. –

0
var mock = new Moq.Mock<RenewalService> { CallBase = true }; 
mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null); 
var results = mock.Object.IsLastRenewalOfYear(...); 
Cuestiones relacionadas