web-dev-qa-db-fra.com

La méthode statique mocking de PowerMockito échoue lors de l'appel de la méthode sur un paramètre

J'essaie de tester une classe qui utilise une classe de calculatrice avec un certain nombre de méthodes statiques. J'ai réussi à me moquer d'une autre classe de la même manière, mais celle-ci s'avère plus têtue.

Il semble que si la méthode simulée contient un appel de méthode sur l'un des arguments passés, la méthode statique n'est pas simulée (et le test est interrompu). Supprimer l'appel interne n'est clairement pas une option. Y a-t-il quelque chose d'évident qui me manque ici?

Voici une version condensée qui se comporte de la même manière ...

public class SmallCalculator {

    public static int getLength(String string){

        int length = 0;

        //length = string.length(); // Uncomment this line and the mocking no longer works... 

        return length;
    }
}

Et voici le test ...

import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;

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;

import com.solveit.aps.transport.model.impl.SmallCalculator;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ SmallCalculator.class})
public class SmallTester {

    @Test
    public void smallTest(){

        PowerMockito.spy(SmallCalculator.class);
        given(SmallCalculator.getLength(any(String.class))).willReturn(5);

        assertEquals(5, SmallCalculator.getLength(""));
    }
}

Il semble y avoir une certaine confusion dans la question, alors j’ai imaginé un exemple plus «réaliste». Celui-ci ajoute un niveau d'indirection, de sorte qu'il ne semble pas que je teste directement la méthode simulée. La classe SmallCalculator est inchangée:

public class BigCalculator {

    public int getLength(){

        int length  = SmallCalculator.getLength("random string");

        // ... other logic

        return length;
    }

    public static void main(String... args){

        new BigCalculator();
    }
}

Et voici la nouvelle classe de test ...

import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;

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;

import com.solveit.aps.transport.model.impl.BigCalculator;
import com.solveit.aps.transport.model.impl.SmallCalculator;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ SmallCalculator.class})
public class BigTester {

    @Test
    public void bigTest(){

        PowerMockito.spy(SmallCalculator.class);
        given(SmallCalculator.getLength(any(String.class))).willReturn(5);

        BigCalculator bigCalculator = new BigCalculator();
        assertEquals(5, bigCalculator.getLength());
    }
}
8
maccaroo

J'ai trouvé la réponse ici https://blog.codecentric.de/fr/2011/11/testing-and-mocking-of-static-methods-in-Java/

Voici le code final qui fonctionne. J'ai testé cette approche dans le code d'origine (ainsi que l'exemple artificiel) et cela fonctionne très bien. Simples ...

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;

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({ SmallCalculator.class})
public class BigTester {

    @Test
    public void bigTest(){

        PowerMockito.mockStatic(SmallCalculator.class);
        PowerMockito.when(SmallCalculator.getLength(any(String.class))).thenReturn(5);

        BigCalculator bigCalculator = new BigCalculator();
        assertEquals(5, bigCalculator.getLength());
    }
}
16
maccaroo

Utilisez anyString() au lieu de any(String.class)

Lorsque vous utilisez any(String.class), l'argument passé est null, car Mockito renverra la valeur par défaut du type de référence, à savoir null. En conséquence, vous obtenez une exception.

Lors de l'utilisation de anyString(), l'argument passé sera une chaîne vide.

Notez que ceci explique pourquoi vous obtenez une exception. Cependant, vous devez revoir la façon dont vous testez votre méthode, comme expliqué dans d'autres commentaires et réponses.

1
vtor

Si vous ne voulez pas que cette méthode soit invoquée. Au lieu de

when(myMethodcall()).thenReturn(myResult);

utilisation 

doReturn(myResult).when(myMethodCall());

C'est une magie moqueuse et il est difficile d'expliquer pourquoi cela fonctionne réellement.

Une autre chose que vous avez oublié est mockStatic(SmallCalculator.class)

Et vous n'avez pas besoin de PowerMockito.spy(SmallCalculator.class);

0
talex