web-dev-qa-db-fra.com

Méthodes de simulation d'objets de portée locale avec Mockito

J'ai besoin d'aide pour cela:

Exemple:

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

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

37
Xoke

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

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

22
Mr.Eddart

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

Voici une solution basée sur cela. Reprenant le code de la question:

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

Le test suivant créera une maquette de la classe d'instance MyObject en préparant la classe qui l'instancie (dans cet exemple, je l'appelle MyClass) avec PowerMock et en laissant PowerMockito stub le constructeur de la classe MyObject, puis en vous permettant de stub la méthode d'instance MyObject1 () appel:

@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 appel interne method1 () retournera ce que vous voulez.

Si vous aimez les monolignes, vous pouvez raccourcir le code en créant la maquette et le talon en ligne:

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

En aucune façon. Vous aurez besoin d'une injection de dépendance, c'est-à-dire qu'au lieu d'avoir instancié l'obj1, il devrait être fourni par une usine.

MyObjectFactory factory;

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

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

Votre test ressemblerait alors à:

@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
}
13
Boris Pavlović

Vous pourriez éviter de changer le code (bien que je recommande la réponse de Boris) et se moquer du constructeur, comme dans cet exemple pour se moquer de la création d'un objet File à l'intérieur d'une méthode. N'oubliez pas pour mettre la classe qui va créer le fichier dans le @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);
    }

}
2
cahen

Vous pouvez le faire en créant une méthode d'usine dans MyObject:

class MyObject {
    public static MyObject create() {
      return new MyObject();
    }
}

puis moquer cela avec PowerMock .

Cependant, en se moquant des méthodes d'un objet de portée locale, vous dépendez de la partie de l'implémentation de la méthode qui reste la même. Vous perdez donc la possibilité de refactoriser cette partie de la méthode sans casser le test. En outre, si vous stubez les valeurs de retour dans la maquette, votre test unitaire peut réussir, mais la méthode peut se comporter de manière inattendue lors de l'utilisation de l'objet réel.

En somme, vous ne devriez probablement pas essayer de le faire. Plutôt, en laissant le test conduire votre code (alias TDD), vous arriveriez à une solution comme:

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

en passant la dépendance, que vous pouvez facilement simuler pour le test unitaire.

1
Garrett Hall