J'ai une classe Java nommée MyClass
que je veux tester avec JUnit. La méthode publique, methodA
, que je veux tester appelle une méthode privée, methodB
, dans la même classe pour déterminer le chemin d'accès conditionnel à suivre. Mon but est d'écrire des tests JUnit pour les différents chemins dans methodA
. De plus, methodB
appelle un service. Par conséquent, je ne souhaite pas qu'il soit réellement exécuté lorsque j'exécute les tests JUnit.
Quel est le meilleur moyen de se moquer de methodB
et de contrôler son retour afin que je puisse tester différents chemins pour 'méthodeA'?
Je préfère utiliser JMockit pour écrire des simulacres. Je suis donc particulièrement intéressé par toute réponse qui s'applique à JMockit.
Voici mon exemple de classe:
public class MyClass {
public String methodA(CustomObject object1, CustomObject object2) {
if(methodB(object1, object2)) {
// Do something.
return "Result";
}
// Do something different.
return "Different Result";
}
private boolean methodB(CustomObject custObject1, CustomObject custObject2) {
/* For the sake of this example, assume the CustomObject.getSomething()
* method makes a service call and therefore is placed in this separate
* method so that later an integration test can be written.
*/
Something thing1 = cobject1.getSomething();
Something thing2 = cobject2.getSomething();
if(thing1 == thing2) {
return true;
}
return false;
}
}
Voici ce que j'ai jusqu'à présent:
public class MyClassTest {
MyClass myClass = new MyClass();
@Test
public void test_MyClass_methodA_enters_if_condition() {
CustomObject object1 = new CustomObject("input1");
CustomObject object2 = new CustomObject("input2");
// How do I mock out methodB here to return true?
assertEquals(myClass.methodA(object1, object2), "Result");
}
@Test
public void test_MyClass_methodA_skips_if_condition() {
CustomObject object1 = new CustomObject("input1");
CustomObject object2 = new CustomObject("input2");
// How do I mock out methodB here to return false?
assertEquals(myClass.methodA(object1, object2), "Different Result");
}
}
Merci!
Pour donner la réponse que vous avez demandée (en se moquant en partie de JMockit):
public class MyClassTest
{
@Tested MyClass myClass;
@Test
public void test_MyClass_methodA_enters_if_condition() {
final CustomObject object1 = new CustomObject("input1");
final CustomObject object2 = new CustomObject("input2");
new NonStrictExpectations(myClass) {{
invoke(myClass, "methodB", object1, object2); result = true;
}};
assertEquals("Result", myClass.methodA(object1, object2));
}
@Test
public void test_MyClass_methodA_skips_if_condition() {
final CustomObject object1 = new CustomObject("input1");
final CustomObject object2 = new CustomObject("input2");
new NonStrictExpectations(myClass) {{
invoke(myClass, "methodB", object1, object2); result = false;
}};
assertEquals("Different Result", myClass.methodA(object1, object2));
}
}
Cependant, je not ne le recommanderais pas. En général, les méthodes private
ne doivent pas être moquées. À la place, simulez la dépendance externe réelle de votre unité testée (la CustomObject
dans ce cas):
public class MyTestClass
{
@Tested MyClass myClass;
@Mocked CustomObject object1;
@Mocked CustomObject object2;
@Test
public void test_MyClass_methodA_enters_if_condition() {
new NonStrictExpectations() {{
Something thing = new Something();
object1.getSomething(); result = thing;
object2.getSomething(); result = thing;
}};
assertEquals("Result", myClass.methodA(object1, object2));
}
@Test
public void test_MyClass_methodA_skips_if_condition() {
new NonStrictExpectations() {{
object1.getSomething(); result = new Something();
object2.getSomething(); result = new Something();
}};
assertEquals("Different Result", myClass.methodA(object1, object2));
}
}
Ne soyez pas tenté de vous moquer des méthodes privées, même si vous pouvez vous lancer dans la supercherie en utilisant un outil moqueur. Les membres privés sont des détails de mise en œuvre, que vous devriez être libre de modifier. Utilisez plutôt l'API non privée pour exercer la classe. Si cela vous pose problème, envisagez de déplacer le code problématique dans une classe différente, si ce n’est pas déjà fait, et utilisez l’injection de dépendances pour injecter une implémentation fictive du code problématique.
Faites de la méthode B un membre d'une classe distincte et indiquez une référence privée à cette classe dans MyClass
.
public class MyClass {
private MyOtherClass otherObject = new MyOtherClass();
public String methodA(CustomObject object1, CustomObject object2) {
if(otherObject.methodB(object1, object2)) {
// Do something.
return "Result";
}
// Do something different.
return "Different Result";
}
}
class MyOtherClass {
public boolean methodB(CustomObject custObject1, CustomObject custObject2) {
// Yada yada code
}
}
Personnellement, je teste habituellement uniquement les méthodes publiques et examine les rapports de couverture pour vérifier que tous les chemins ont été visités dans mes méthodes privées. Si j'ai vraiment besoin de tester une méthode privée, c'est une odeur qui nécessite une refactorisation comme celle décrite ci-dessus.
Vous pouvez également utiliser la réflexion, mais je me sentirais sale en le faisant. Si vous voulez VRAIMENT la solution, faites le moi savoir et je l’ajouterai à cette réponse.
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
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({ MyClass.class })
public class MyClassTest {
// Class Under Test
MyClass cut;
@Before
public void setUp() {
// Create a new instance of the service under test (SUT).
cut = new MyClass();
// Common Setup
// TODO
}
@Test
public void testMethodA() throws Exception {
/* Initialization */
CustomObject object2 = PowerMock.createNiceMock(CustomObject.class);
CustomObject object1 = PowerMock.createNiceMock(CustomObject.class);
MyClass partialMockCUT = PowerMock.createPartialMock(MyClass.class,
"methodB");
long response = 1;
/* Mock Setup */
PowerMock
.expectPrivate(partialMockCUT, "methodB",
EasyMock.isA(CustomObject.class),
EasyMock.isA(CustomObject.class)).andReturn(true)
.anyTimes();
/* Mock Setup */
/* Activate the Mocks */
PowerMock.replayAll();
/* Test Method */
String result = partialMockCUT.methodA(object1, object2);
/* Asserts */
Assert.assertNotNull(result);
PowerMock.verifyAll();
}
}