webentwicklung-frage-antwort-db.com.de

Mock private Methode in derselben Klasse, die gerade getestet wird

Ich habe eine Java-Klasse namens MyClass, die ich mit JUnit testen möchte. Die öffentliche Methode, methodA, die ich testen möchte, ruft eine private Methode, methodB, in derselben Klasse auf, um zu bestimmen, welchem ​​bedingten Pfad gefolgt werden soll. Mein Ziel ist es, JUnit-Tests für die verschiedenen Pfade in methodA zu schreiben. Außerdem ruft methodB einen Dienst auf. Ich möchte also nicht, dass er tatsächlich ausgeführt wird, wenn ich die JUnit-Tests durchführe.

Was ist der beste Weg, um methodB zu verspotten und seine Rückkehr zu steuern, damit ich verschiedene Pfade für 'methodA' testen kann?

Ich ziehe es vor, JMockit beim Schreiben von Mocks zu verwenden, deshalb bin ich besonders an jeder Antwort interessiert, die auf JMockit zutrifft.

Hier ist meine Beispielklasse:

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;
    }

}

Das habe ich bisher:

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");
    }

}

Vielen Dank!

9
Kingand

Um die gewünschte Antwort zu geben (unter Verwendung von JMockits partiellem Spott):

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));
    }
}

Ich würde jedoch nicht es so empfehlen. Im Allgemeinen sollten private-Methoden nicht verspottet werden. Mock stattdessen die tatsächliche externe Abhängigkeit Ihrer zu testenden Einheit (in diesem Fall die Variable CustomObject):

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));
    }
}
2
Rogério

Seien Sie nicht versucht, sich über private Methoden lustig zu machen, selbst wenn Sie sich dazu mit einem Spott-Werkzeug in die Irre führen können. Private Mitglieder sind Implementierungsdetails, die Sie ändern können. Verwenden Sie stattdessen die nicht private API, um die Klasse auszuüben. Wenn dies problematisch ist, ziehen Sie in Erwägung, den problematischen Code in eine andere Klasse zu verschieben, falls noch nicht vorhanden, und verwenden Sie die Abhängigkeitseinspritzung, um eine Scheinimplementierung des problematischen Codes einzufügen.

3
Raedwald

Machen Sie methodB zu einem Member einer separaten Klasse und haben Sie eine private Referenz auf diese Klasse in 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
    }
}

Ich persönlich teste normalerweise nur öffentliche Methoden und schaue in Berichterstattungsberichten, um sicherzustellen, dass alle Pfade in meinen privaten Methoden besucht wurden. Wenn ich wirklich eine private Methode testen muss, ist dies ein Geruch, der wie oben beschrieben ein Refactoring erfordert.

Sie könnten auch Reflexion verwenden, aber ich würde mich dabei schmutzig fühlen. Wenn Sie wirklich die Lösung wollen, lassen Sie es mich wissen und ich füge sie dieser Antwort hinzu.

0
mikeslattery
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();

}

}
0
javaPlease42