web-dev-qa-db-fra.com

Se moquer d'une variable privée supposée exister

Comment pouvez-vous obtenir un objet simulé au moment de l'exécution lorsqu'il n'est pas créé/initialisé dans la classe que vous testez, qu'il n'est pas statique (modèle singleton) ou que vous n'avez pas de constructeur de test auquel vous connecter?

Dans une classe pour laquelle j'écris des tests unitaires, j'ai rencontré un scénario que je n'ai pas encore rencontré/résolu. J'ai une ressource JMS (un QueueConnectionFactory pour référence, mais cela ne devrait pas avoir d'importance), c'est une variable privée de la classe que je teste. Comme il a l'annotation javax.annotation.Resource, Il est supposé disponible à l'exécution. Pendant les tests, ce n'est pas le cas, ce qui crée le besoin de se moquer de cet objet.

Ce n'est pas une classe statique et n'est pas utilisé de manière statique, si c'était le cas, je pourrais facilement me moquer en utilisant les différentes méthodes de mockage statique que j'ai rencontrées. Étant donné que la ressource n'est jamais créée localement (dans un constructeur ou même dans un constructeur de test), je n'ai aucun moyen de transmettre un objet Mock de sorte qu'au moment de l'exécution du test, la maquette soit utilisée à la place de l'objet réel. Comment puis-je me moquer de cette ressource de sorte que lorsque le test s'exécute, il sera utilisé à la place de l'objet privé @Resource Dans la classe que je teste?

Pour référence, le code appelle createConnection() sur le QueueConnectionFactory qui lève une exception de pointeur nul car la Factory n'a pas été initialisée/mockée.

@Stateless
public class Example{
  @Resource(name = "jms/exampleQCF")
  private QueueConnectionFactory queueFactory;

  ...

  public void testMe(){
    Connection connection = queueFactory.createConnection();
    ...
  }
}
19
Walls

Après beaucoup plus de chasse et en regardant toutes les options que Mockito/Powermock avait à offrir, j'ai trouvé la solution (que je partagerai au cas où d'autres rencontreraient ce même problème).

Lorsque vous avez des variables membres privées qui ne sont jamais initialisées (et simplement supposées créées à d'autres endroits), vous pouvez utiliser l'annotation @InjectMocks Pour "injecter" les Mocks que vous voulez dans votre classe que vous testez.

  1. Ajoutez une variable dans votre classe de test pour la classe que vous testez et donnez-lui l'annotation @InjectMocks (Org.Mockito.InjectMocks).
  2. Utilisez les annotations @Mock Pour configurer les mocks que vous souhaitez injecter. Utilisez la propriété de nom @Mock (name = "privateVariableNameHere") pour mapper l'objet Mock à la variable privée à l'intérieur de votre classe que vous testez.
  3. Dans une fonction de configuration ou avant d'appeler votre classe, initialisez les simulations. Le moyen le plus simple que j'ai trouvé est d'utiliser une méthode "setup" avec l'annotation @Before. Puis à l'intérieur, appelez MockitoAnnotations.initMocks(this); pour initialiser rapidement n'importe quoi avec l'annotation @Mock.
  4. Définissez votre fonctionnalité Mock dans votre méthode de test (avant d'appeler la méthode que vous testez).
  5. À l'aide de l'objet @InjectMock, Appelez la méthode que vous testez ... les maquettes DEVRAIENT être connectées et fonctionner comme défini dans les étapes précédentes.

Ainsi, pour l'exemple de classe que j'utilise ci-dessus, le code à tester/mock aurait renvoyé Connection sous forme de mock avec lequel vous pouvez faire n'importe quoi. Sur la base de l'exemple ci-dessus dans ma question, voici à quoi ressemblerait le code:

@RunWith(PowerMockRunner.class)
@PrepareForTest({/* Static Classes I am Mocking */})
public class ExampleTest {
  @Mock (name = "queueFactory") //same name as private var.
  QueueConnectionFactory queueFactoryMock;
  @Mock
  Connection connectionMock; //the object we want returned
  @InjectMocks
  Example exampleTester; //the class to test

  @Before
  public void setup(){
    MockitoAnnotations.initMocks(this); // initialize all the @Mock objects
    // Setup other Static Mocks
  }

  @Test
  public void testTestMe(){
    //Mock your objects like other "normally" mocked objects
    PowerMockito.when(queueFactoryMock.createConnection()).thenReturn(connectionMock);
    //...Mock ConnectionMock functionality...
    exampleTester.testMe();
  }
}
38
Walls

Plusieurs approches ici:

  1. ReflectionTestUtils du framework Spring Testing: ReflectionTestUtils.setField(objectToTest, "privateFieldName", mockObjectToInject);. Avec cela, vous n'introduisez pas une autre dépendance.
  2. org.mockito.internal.util.reflection.FieldSetter.
  3. PowerMock.Whitebox.setInternalState() pour se moquer d'un champ privé.

Si vous devez simuler la création de variables locales internes, utilisez PowerMockito.whenNew (Foo.class) .withNoArguments (). ThenReturn (foo); `. Très, très utile. Impossible de trouver d'autres façons de faire de même.

Avec Mockito uniquement, vous ne pouvez pas simuler la création de variables locales, car when(any(Foo.class) ne fonctionne pas; renverra null. Il compile mais ne fonctionne pas.

Références: Mockito: Mock private field initialization

0
WesternGun