78 votes

Mocking de méthodes d'objets de portée locale avec Mockito

J'ai besoin d'aide à ce sujet :

Exemple :

void method1{
    MyObject obj1=new MyObject();
    obj1.method1();
}

Je veux me moquer obj1.method1() dans mon test mais pour être transparent, je ne veux pas faire de changement de code. Y a-t-il un moyen de faire cela dans Mockito ?

73voto

raspacorp Points 351

La réponse de @edutesoy pointe vers la documentation de PowerMockito et mentionne le mocking des constructeurs comme un indice mais ne mentionne pas comment l'appliquer au problème actuel de la question.

Voici une solution basée sur ce principe. En prenant le code de la question :

public class MyClass {
    void method1 {
        MyObject obj1 = new MyObject();
        obj1.method1();
    }
}

Le test suivant créera une copie de l'interface utilisateur de l'application MyObject en préparant la classe d'instance qui l'instancie (dans cet exemple je l'appelle MyClass ) avec PowerMock et en laissant PowerMockito pour stub le constructeur de MyObject puis vous permet de bloquer la classe MyObject instance method1() appeler :

@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)
public class MyClassTest {
    @Test
    public void testMethod1() {      
        MyObject myObjectMock = mock(MyObject.class);
        when(myObjectMock.method1()).thenReturn(<whatever you want to return>);   
        PowerMockito.whenNew(MyObject.class).withNoArguments().thenReturn(myObjectMock);

        MyClass objectTested = new MyClass();
        objectTested.method1();

        ... // your assertions or verification here 
    }
}

Avec cela, votre interne method1() retournera ce que vous voulez.

Si vous aimez les lignes uniques, vous pouvez raccourcir le code en créant l'objet fantaisie et le stub en ligne :

MyObject myObjectMock = when(mock(MyObject.class).method1()).thenReturn(<whatever you want>).getMock();

34voto

edutesoy Points 3024

Si vous voulez vraiment éviter de toucher à ce code, vous pouvez utiliser Powermockito (PowerMock pour Mockito).

Avec cela, parmi beaucoup d'autres choses, vous pouvez simuler la construction de nouveaux objets d'une manière très simple.

20voto

Boris Pavlović Points 22207

Pas question. Vous aurez besoin d'une injection de dépendance, c'est-à-dire qu'au lieu d'avoir l'élément obj1 instancié, il doit être fourni par une usine.

MyObjectFactory factory;

public void setMyObjectFactory(MyObjectFactory factory)
{
  this.factory = factory;
}

void method1()
{
  MyObject obj1 = factory.get();
  obj1.method();
}

Alors votre test ressemblerait à ceci :

@Test
public void testMethod1() throws Exception
{
  MyObjectFactory factory = Mockito.mock(MyObjectFactory.class);
  MyObject obj1 = Mockito.mock(MyObject.class);
  Mockito.when(factory.get()).thenReturn(obj1);

  // mock the method()
  Mockito.when(obj1.method()).thenReturn(Boolean.FALSE);

  SomeObject someObject = new SomeObject();
  someObject.setMyObjectFactory(factory);
  someObject.method1();

  // do some assertions
}

3voto

cahen Points 642

Vous pouvez éviter de modifier le code (bien que je recommande la réponse de Boris) et simuler le constructeur, comme dans cet exemple pour simuler la création d'un objet Fichier dans une méthode. N'oubliez pas pour mettre la classe qui créera le fichier dans le fichier @PrepareForTest .

package hello.easymock.constructor;

import java.io.File;

import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({File.class})
public class ConstructorExampleTest {

    @Test
    public void testMockFile() throws Exception {

        // first, create a mock for File
        final File fileMock = EasyMock.createMock(File.class);
        EasyMock.expect(fileMock.getAbsolutePath()).andReturn("/my/fake/file/path");
        EasyMock.replay(fileMock);

        // then return the mocked object if the constructor is invoked
        Class<?>[] parameterTypes = new Class[] { String.class };
        PowerMock.expectNew(File.class, parameterTypes , EasyMock.isA(String.class)).andReturn(fileMock);
        PowerMock.replay(File.class); 

        // try constructing a real File and check if the mock kicked in
        final String mockedFilePath = new File("/real/path/for/file").getAbsolutePath();
        Assert.assertEquals("/my/fake/file/path", mockedFilePath);
    }
}

3voto

Rahul Dey Points 103

Si vous ne préférez pas utiliser PowerMock, vous pouvez essayer la méthode suivante :

public class Example{
...
void method1(){
    MyObject obj1 = getMyObject();
    obj1.doSomething();
}

protected MyObject getMyObject(){
    return new MyObject();
}
...
}

Écrivez votre test comme ceci :

@Mock
MyObject mockMyObject;

@Test
void testMethod1(){
    Example spyExample = spy(new Example());
    when(spyExample.getMyObject()).thenReturn(mockMyObject);
    //stub if required
    doNothing().when(mockMyObject.doSomething());
    verify(mockMyObject).doSomething();
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X