Search code examples
javaunit-testingjunitjmockit

JMockit injecting nested member variables


I am currently attempting to test the execution path of my code, but I ran into some issues when it came to nested class member variables. Consider the following class structure:

System (System under test)
 -> ClassA
   -> ClassB
     -> ClassC

A class named System contains an instance of ClassA, and will invoke one of its methods. ClassA will similarly contain an instance of ClassB and will invoke one of it's methods, etc.

@RunWith(JMockit.class)
public class SomeTest {
    @Tested
    private System system;

    @Injectable
    private ClassA memberA;

    @Injectable
    private ClassB memberB;

    @Injectable
    private ClassC memberC;

    @Test
    public void doSystemTest() {
        // Do stuff
        system.handle();

        new Verifications() {
            {
                system.handle(); times = 1;
                memberA.doSomeAStuff(); times = 1;
                memberB.doSomeBStuff(); times = 1; // Results in test failure: 
            }
        };
    }
}

When I run this test, I get the following error:

Missing 1 invocation to: ca.kgli.jmockit.ClassB#doSomeBStuff() on mock instance: ca.kgli.jmockit.ClassB@1a052a00

For reference, here is the code:

public class System {
    private ClassA memberA;

    public void handle() {
        memberA.doSomeAStuff();
    }
}

public class ClassA {
    private ClassB memberB;

    public void doSomeAStuff() {
        memberB.doSomeBStuff();
    }
}

public class ClassB {
    private ClassC member;

    public void doSomeBStuff() {
        member.stuffCSays("arsenic");
    }
}

public class ClassC {
    public String stuffCSays(String input) {
        return "Hello, " + input;
    }
}

So my question is, why is the method doSomeBStuff not being invoked? Was JMockit not meant to be used in this way? I'm new to this test toolkit so any help or insight into this problem would be greatly appreciated.


Solution

  • When you mock ClassA, the real implemention of doSomeAStuff() is intercepted by Jmockit, so it will not call doSomeBStuff().

    You should only mock ClassA when you perform unit test on System.

    @RunWith(JMockit.class)
    public class SomeTest {
        @Tested
        private System1 system;
    
        @Injectable
        private ClassA memberA;
    
        @Test
        public void doSystemTest() {
            // Do stuff
            system.handle();
    
            new Verifications() {
                {
                    memberA.doSomeAStuff(); times = 1;
                }
            };
        }
    }
    

    The mock of ClassB should only occur when you perform unit test on ClassA.