Método privado burlado con PowerMock, pero el método subyacente todavía se llama


Estoy tratando de burlarme de un método privado que está haciendo una llamada JNDI. Cuando ese método es llamado desde una prueba unitaria, lanza una excepción^. Me gustaría simular ese método con fines de prueba. Usé el código de ejemplo de otra respuesta de preguntas, y mientras la prueba pasa, parece que el método subyacente todavía se llama. Inserté un System.err.println() en el método doTheGamble(), y se imprime en mi consola.

Bastante interesante, si comento el primero assertThat, la prueba pasa. ?:(

Entonces, ¿cómo me burlo de un método privado para que no sea llamado?

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.support.membermodification.MemberMatcher.method;

import java.util.Random;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(CodeWithPrivateMethod.class)
public class PowerMock_Test {

    static boolean gambleCalled = false; 

    @Test(expected = RuntimeException.class)
    public void when_gambling_is_true_then_always_explode() throws Exception {
        CodeWithPrivateMethod spy = PowerMockito.spy(new CodeWithPrivateMethod());

        when(spy, method(CodeWithPrivateMethod.class, "doTheGamble", String.class, int.class))
                .withArguments(anyString(), anyInt())
                .thenReturn(true);

/* 1 */ assertThat( PowerMock_Test.gambleCalled, is(false) );
        spy.meaningfulPublicApi();
/* 2 */ assertThat( PowerMock_Test.gambleCalled, is(false) );
    }
}


class CodeWithPrivateMethod {

    public void meaningfulPublicApi() {
        if (doTheGamble("Whatever", 1 << 3)) {
            throw new RuntimeException("boom");
        }
    }

    private boolean doTheGamble(String whatever, int binary) {
        Random random = new Random(System.nanoTime());
        boolean gamble = random.nextBoolean();

        System.err.println( "\n>>> GAMBLE CALLED <<<\n" );
        PowerMock_Test.gambleCalled = true;

        return gamble;
    }
}   

^ comprensiblemente, dado que mi espacio de trabajo no admite JNDI, solo el entorno de producción lo hace

% Estoy usando las últimas versiones de toda la biblioteca, JUnit 4.10, Mockito 1.8.5, Hamcrest 1.1, Javassist 3.15.0 y PowerMock 1.4.10.

Author: Community, 2011-11-09

4 answers

Del Método Privado de PowerMock Ejemplo :

@RunWith(PowerMockRunner.class)
// We prepare PartialMockClass for test because it's final or we need to mock private or static methods
@PrepareForTest(PartialMockClass.class)
public class YourTestCase {
@Test
public void privatePartialMockingWithPowerMock() {        
    PartialMockClass classUnderTest = PowerMockito.spy(new PartialMockClass());

    // use PowerMockito to set up your expectation
    PowerMockito.doReturn(value).when(classUnderTest, "methodToMock", "parameter1");

    // execute your test
    classUnderTest.execute();

    // Use PowerMockito.verify() to verify result
    PowerMockito.verifyPrivate(classUnderTest, times(2)).invoke("methodToMock", "parameter1");
}

Así que para aplicar esto a su código, creo que podría convertirse en:

@RunWith(PowerMockRunner.class)
@PrepareForTest(CodeWithPrivateMethod.class)
public class PowerMock_Test {
    @Test(expected = RuntimeException.class)
    public void when_gambling_is_true_then_always_explode() throws Exception {
        CodeWithPrivateMethod spy = PowerMockito.spy(new CodeWithPrivateMethod());

        PowerMockito.doReturn(true).when(spy, "doTheGamble", anyString(), anyInt());


/* 1 */ PowerMockito.verifyPrivate(spy, times(0)).invoke("doTheGamble", anyString(), anyInt());            
        spy.meaningfulPublicApi();
/* 2 */ PowerMockito.verifyPrivate(spy, times(2)).invoke("doTheGamble", anyString(), anyInt());            
    }
}

Acabo de codificarlo en el editor aquí. En realidad no se han ejecutado pruebas, y no se han dañado errores en la elaboración de este código.

 33
Author: Mike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-08 22:19:32

ArtB,

Simplemente pegando el código completo que funciona bien en mi IDE Eclipse. Solo he cambiado la expectativa que dije en mi último post. Buena suerte.

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.powermock.api.support.membermodification.MemberMatcher.method;

import java.util.Random;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(CodeWithPrivateMethod.class)
public class PowerMock_Test {

    static boolean gambleCalled = false; 

    @Test(expected = RuntimeException.class)
    public void when_gambling_is_true_then_always_explode() throws Exception {
        CodeWithPrivateMethod spy = PowerMockito.spy(new CodeWithPrivateMethod());

//        PowerMockito.doReturn(true).when(spy, "doTheGamble", anyString(), anyInt());

        PowerMockito.doReturn(true).when(spy, 
               method(CodeWithPrivateMethod.class, "doTheGamble", String.class, int.class))
                .withArguments(anyString(), anyInt());

        assertThat( PowerMock_Test.gambleCalled, is(false) );
        spy.meaningfulPublicApi();
        assertThat( PowerMock_Test.gambleCalled, is(false) );
    }
}


class CodeWithPrivateMethod {

    public void meaningfulPublicApi() {
        if (doTheGamble("Whatever", 1 << 3)) {
            throw new RuntimeException("boom");
        }
    }

    private boolean doTheGamble(String whatever, int binary) {
        Random random = new Random(System.nanoTime());
        boolean gamble = random.nextBoolean();

        System.err.println( "\n>>> GAMBLE CALLED <<<\n" );
        PowerMock_Test.gambleCalled = true;

        return gamble;
    }
}   
 2
Author: skusunam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-10 16:02:34

ArtB,

¿Estás seguro de que tu código no funciona (o) me estoy perdiendo algo aquí? Acabo de reemplazar su expectativa método con el siguiente de la manera Mike sugirió y funciona bien:

PowerMockito.doReturn(true).when(spy, 
               method(CodeWithPrivateMethod.class, "doTheGamble", String.class, int.class))
                .withArguments(anyString(), anyInt());

Nunca he usado Powermockito pero he usado Mockito mucho antes.

 1
Author: skusunam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-09 22:44:00

Cuando se usa spy para construir un objeto simulado, es un objeto real medio respaldado. Mi conjetura sería deshacerse de spy. Trabajar en puro objeto simulado.

 0
Author: Ravi Bhatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-08 21:59:03