简体   繁体   中英

Mockito spy failed because class member cannot be initialised

I have the following code:

public class MyClass() {
    private MyObject myObject = getMyObject();
    public MyObject getMyObject() {
        if (myObject == null) {
            myObject = MyStaticClass.createMyObject();
        }
        return myObject;
    }
    // heaps more methods
}

public class MyTest() {
    private MyClass spyMyClass = spy(new MyClass());
    public MyTest() {
        doReturn(null).when(spyMyClass).getMyObject();
    }
    @Test
    public void someTest() {
        ClassUnderTest c = new ClassUnderTest();
        assertTrue(c.someMethod(spyMyClass));
    }
}

The test failed with an error Could not initialise class MyStaticClass . The reason being this static class is initialising many more other class objects that are not available during test (eg database) and I do not care as you can see I am ok to return null when the method getMyObject() is called.

But this intention failed as well because before the doReturn(null) line is reached, the test has already failed at the spy(new MyClass()) line, in which it calls getMyObject() to initialise the private member myObject .

A workaround to the above situation is to use mock(MyClass.class) instead of spy(new MyClass()) , so that the private member myObject is not being initialised and hence not calling the real getMyObject() method.

But this workaround creates another headache to me because that means I will have to do some configuration (even just to doCallRealMethod() ) for those heaps more methods within MyClass .

Question: is there another solution that I can still use spy on an instance of MyClass so that I can forget about configuring those heaps more methods within this class, yet I can get around the Could not initialise class MyStaticClass error?

PS I can't simply just use Power Mock to mock MyStaticClass because I am already using another test runner for MyTest . Unless your answer can show how easy it is to run two test runners at the same time without implementing a new hybrid test runner combining both.


Thanks to Adam, now I have a good working code :

public class MyTest() {
    private MyClass spyMyClass = spy(new MyClass() {
        @Override
        public MyObject getMyObject() {
            return null;
        }
    });
    @Test
    public void someTest() {
        ClassUnderTest c = new ClassUnderTest();
        assertTrue(c.someMethod(spyMyClass));
    }
}

Create a subclass of MyClass and use it in private MyClass spyMyClass = spy(new TestMyClass()); :

class TestMyClass extends MyClass {

  @Override // fortunately, the original method called in constructor can be overridden (what could be considered bad)
  public MyObject getMyObject() {
    // something that does not fail the constructor
  }

}

In general, this is a potential cause for problems, as you are calling a non-private, non-final method in your constructor. For the test case it might be acceptable.

Stepping away a bit, I guess it would be wise to take a look at responsibilities of this MyClass object. Isn't it doing too much, therefore it is hard to test and interact with? That often results in "hard-to-mock" syndrome.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM