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();
...
}
}
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.
@InjectMocks
(Org.Mockito.InjectMocks).@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.@Before
. Puis à l'intérieur, appelez MockitoAnnotations.initMocks(this);
pour initialiser rapidement n'importe quoi avec l'annotation @Mock
.@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();
}
}
Plusieurs approches ici:
ReflectionTestUtils
du framework Spring Testing: ReflectionTestUtils.setField(objectToTest, "privateFieldName", mockObjectToInject);
. Avec cela, vous n'introduisez pas une autre dépendance.org.mockito.internal.util.reflection.FieldSetter
.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