2009-07-06 37 views
162

Me gustaría probar una clase abstracta. Claro, puedo manually write a mock que hereda de la clase.Usando Mockito para probar clases abstractas

¿Puedo hacer esto usando un marco de burla (estoy usando Mockito) en lugar de hacer a mano mi simulacro? ¿Cómo?

+2

A partir de Mockito [1.10.12] (http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#30), Mockito admite clases abstractas de espionaje/burla directamente: 'SomeAbstract spy = espía (SomeAbstract.class); ' – pesche

Respuesta

267

La siguiente sugerencia permite probar clases abstractas sin crear una subclase "real": el simulador es la subclase.

usa Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), luego simula cualquier método abstracto invocado.

Ejemplo:

public abstract class My { 
    public Result methodUnderTest() { ... } 
    protected abstract void methodIDontCareAbout(); 
} 

public class MyTest { 
    @Test 
    public void shouldFailOnNullIdentifiers() { 
     My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS); 
     Assert.assertSomething(my.methodUnderTest()); 
    } 
} 

Nota: La belleza de esta solución es que no haya para implementar los métodos abstractos, siempre y cuando no se invocan.

En mi opinión honesta, esto es más limpio que usar un espía, ya que un espía requiere una instancia, lo que significa que tiene que crear una subclase instanciable de su clase abstracta.

+0

+1 por ayudarme a resolver mi problema que no estaba relacionado con la pregunta. CALLS_REAL_METHODS fue útil para inyectar un objeto stub en mi SUT. Un stub tenía más sentido que un simulacro ya que los valores de retorno eran un poco complejos. – Snekse

+10

Como se indica a continuación, esto no funciona cuando la clase abstracta llama a métodos abstractos para ser probado, que a menudo es el caso. –

+6

Esto realmente funciona cuando la clase abstracta llama a métodos abstractos. Solo use la sintonía doReturn o doNothing en lugar de Mockito.when para copiar los métodos abstractos, y si resuelve cualquier llamada concreta, asegúrese de que el resumen de las llamadas abstractas es lo primero. –

2

Suponiendo sus clases de prueba están en el mismo paquete (bajo una raíz de origen diferente) como sus clases bajo prueba se puede simplemente crear la maqueta:

YourClass yourObject = mock(YourClass.class); 

y llamar a los métodos que desea probar tal y como lo sería cualquier otro método.

Debe proporcionar las expectativas para cada método que se llama con la expectativa de cualquier método concreto que llame al método súper - no estoy seguro de cómo haría eso con Mockito, pero creo que es posible con EasyMock.

Todo lo que hace es crear una instancia concreta de YouClass y ahorrarle el esfuerzo de proporcionar implementaciones vacías de cada método abstracto.

Como nota aparte, a menudo me resulta útil implementar la clase abstracta en mi prueba, donde sirve como ejemplo de implementación que pruebo a través de su interfaz pública, aunque esto depende de la funcionalidad proporcionada por la clase abstracta.

+3

Pero usar el simulacro no probará los métodos concretos de YourClass, ¿o estoy equivocado? Esto no es lo que busco. – ripper234

+1

Correcto, lo anterior no funcionará si desea invocar los métodos concretos en la clase abstracta. –

+0

Disculpas, voy a editar el bit sobre la expectativa, que se requieren para cada método que llame no solo los abstractos. –

13

Los marcos de burla están diseñados para facilitar el cálculo de las dependencias de la clase que está probando. Cuando utiliza un marco de burla para simular una clase, la mayoría de los marcos crean dinámicamente una subclase y reemplazan la implementación del método con código para detectar cuándo se llama un método y devolver un valor falso.

Al probar una clase abstracta, desea ejecutar los métodos no abstractos del Sujeto en prueba (SUT), por lo que un marco de burla no es lo que desea.

Parte de la confusión es que la respuesta a la pregunta con la que te vinculaste decía que se realizaba a mano un simulacro que se extiende desde tu clase abstracta. No llamaría a esa clase una burla. Un simulacro es una clase que se usa como reemplazo de una dependencia, se programa con expectativas y se puede consultar para ver si se cumplen esas expectativas.

En su lugar, sugiero que defina una subclase no abstracta de su clase abstracta en su prueba. Si eso resulta en demasiados códigos, entonces eso puede ser una señal de que su clase es difícil de extender.

Una solución alternativa sería hacer que su caso de prueba sea abstracto, con un método abstracto para crear el SUT (en otras palabras, el caso de prueba usaría el patrón de diseño Template Method).

6

Intente utilizar una respuesta personalizada.

Por ejemplo:

import org.mockito.Mockito; 
import org.mockito.invocation.InvocationOnMock; 
import org.mockito.stubbing.Answer; 

public class CustomAnswer implements Answer<Object> { 

    public Object answer(InvocationOnMock invocation) throws Throwable { 

     Answer<Object> answer = null; 

     if (isAbstract(invocation.getMethod().getModifiers())) { 

      answer = Mockito.RETURNS_DEFAULTS; 

     } else { 

      answer = Mockito.CALLS_REAL_METHODS; 
     } 

     return answer.answer(invocation); 
    } 
} 

Se devolverá el simulacro de métodos abstractos y llamará al método real para métodos concretos.

15

Puede lograr esto utilizando un espía (sin embargo, utilice la última versión de Mockito 1.8+).

public abstract class MyAbstract { 
    public String concrete() { 
    return abstractMethod(); 
    } 
    public abstract String abstractMethod(); 
} 

public class MyAbstractImpl extends MyAbstract { 
    public String abstractMethod() { 
    return null; 
    } 
} 

// your test code below 

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl()); 
doReturn("Blah").when(abstractImpl).abstractMethod(); 
assertTrue("Blah".equals(abstractImpl.concrete())); 
5

Lo que realmente me hace sentir mal por burlarse de las clases abstractas es el hecho de que ni el constructor por defecto YourAbstractClass() es llamado (super() en el simulacro que falta) ni parece que haya ninguna forma en Mockito por defecto inicialice las propiedades falsas (p. ej. Propiedades de lista con ArrayList vacía o LinkedList).

Mi clase abstracta (básicamente el código fuente de clase se genera) NO proporciona una inyección setter de dependencia para elementos de lista, ni un constructor donde inicializa los elementos de lista (que traté de agregar manualmente).

Solo los atributos de clase usan la inicialización predeterminada: private List dep1 = new ArrayList; private List dep2 = new ArrayList

Así que no hay forma de burlarse de una clase abstracta sin usar una implementación de objeto real (por ejemplo, definición de clase interna en clase de prueba unitaria, métodos abstractos generales) y espiar el objeto real (que hace inicialización de campo).

Lástima que solo PowerMock ayudaría más aquí.

66

Si sólo tiene que probar algunos de los métodos concretos sin tocar ninguno de los resúmenes, se puede utilizar CALLS_REAL_METHODS (ver Morten's answer), pero si el método concreto que se está probando llama a algunos de los resúmenes o métodos de interfaz no implementadas, esta no funcionará - Mockito se quejará "No se puede llamar al método real en la interfaz de Java".

(Sí, es un diseño pésimo, pero algunos marcos, por ejemplo, la tapicería 4, tipo de fuerza en usted.)

La solución es revertir este enfoque - utilizar el comportamiento simulacro ordinaria (es decir, todo está burlado/aplastado) y use doCallRealMethod() para llamar explícitamente el método concreto bajo prueba. P.ej.

public abstract class MyClass { 
    @SomeDependencyInjectionOrSomething 
    public abstract MyDependency getDependency(); 

    public void myMethod() { 
     MyDependency dep = getDependency(); 
     dep.doSomething(); 
    } 
} 

public class MyClassTest { 
    @Test 
    public void myMethodDoesSomethingWithDependency() { 
     MyDependency theDependency = mock(MyDependency.class); 

     MyClass myInstance = mock(MyClass.class); 

     // can't do this with CALLS_REAL_METHODS 
     when(myInstance.getDependency()).thenReturn(theDependency); 

     doCallRealMethod().when(myInstance).myMethod(); 
     myInstance.myMethod(); 

     verify(theDependency, times(1)).doSomething(); 
    } 
} 

actualizado para añadir:

Para métodos no vacíos, tendrá que utilizar thenCallRealMethod() lugar, por ejemplo:

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod(); 

De lo contrario Mockito se quejan de "sin terminar trozo detectado."

+6

Esto funcionará en algunos casos, sin embargo, Mockito no llama al constructor de la clase abstracta subyacente con este método. Esto puede causar que el "método real" falle debido a la creación de un escenario inesperado. Por lo tanto, este método tampoco funcionará en todos los casos. –

+2

Sí, no puede contar con el estado del objeto en absoluto, solo se llama al código del método. –

1

se puede extender la clase abstracta con una clase anónima en su prueba Por ejemplo (usando Junit 4):.

private AbstractClassName classToTest; 

@Before 
public void preTestSetup() 
{ 
    classToTest = new AbstractClassName() { }; 
} 

// Test the AbstractClassName methods. 
0

Usted puede crear una instancia de una clase anónima, inyectar su burla y luego probar esa clase .

@RunWith(MockitoJUnitRunner.class) 
public class ClassUnderTest_Test { 

    private ClassUnderTest classUnderTest; 

    @Mock 
    MyDependencyService myDependencyService; 

    @Before 
    public void setUp() throws Exception { 
     this.classUnderTest = getInstance(); 
    } 

    private ClassUnderTest getInstance() { 
     return new ClassUnderTest() { 

      private ClassUnderTest init(
        MyDependencyService myDependencyService 
      ) { 
       this.myDependencyService = myDependencyService; 
       return this; 
      } 

      @Override 
      protected void myMethodToTest() { 
       return super.myMethodToTest(); 
      } 
     }.init(myDependencyService); 
    } 
} 

Tenga en cuenta que la visibilidad debe ser protected para la propiedad myDependencyService de la clase abstracta ClassUnderTest.

Cuestiones relacionadas