2

I am trying to write a test for legacy code that is looking like this:

public class TestedClass {
    private A a = StaticFactory.createA();
    private B b = StaticFactory.createB();
    // No default constructor
    // code using A and B classes
}

As far as my understanding of Mockito goes, I know that I can not mock static methods, however I know that I can use a little trick and externalize creation of this object to package-private methods like this:

public class TestedClass {

    private A a;
    private B b;

    TestedClass() {
        a = createA();
        b = createB();
    }

    A createA() {
        return StaticFactory.createA();
    }

    B createB() {
        return StaticFactory.createB();
    }

    // code using A and B classes
}

But using this construction I am unable to create spy of TestedClass, it has to be already a spy to use doReturn(..) constructions so test like this won`t work:

public class TestedClassTest {

    TestedClass testedClass;

    A mockA;
    B mockB;

    @Before
    public void setUp() {
        mockA = mock(A.class);
        mockB = mock(B.class);

        testedClass = Mockito.spy(new TestedClass());

        doReturn(mockA).when(testedClass).createA();
        doReturn(mockB).when(testedClass).createB();   
    }
}

Is there any other way to change behaviour of createA and createB methods that are getting fired in constructor to ensure that I have mocks instances?

In this case, StaticFactory.createA() is run and it is throwing exception (just under tests), unabling me to finish initialization of tests.

Running pl.tbawor.TestedClassTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.168 sec <<< FAILURE!
shouldTestSomething(pl.tbawor.TestedClassTest)  Time elapsed: 0.114 sec  <<< ERROR!
java.lang.NullPointerException
        at pl.tbawor.StaticFactory.createA(TestedClass.java:32)
        at pl.tbawor.TestedClass.createA(TestedClass.java:14)
        at pl.tbawor.TestedClass.<init>(TestedClass.java:9)
        at pl.tbawor.TestedClassTest.setUp(TestedClassTest.java:26)

My goal is to avoid running original methods for creation A and B objects to prevent exception from being thrown.

Also as this is legacy project, I am unable to add any additional libraries (PowerMock).

I`ll add that TestingClass modification should be as little as possible.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
Tomasz Bawor
  • 1,447
  • 15
  • 40
  • Use PowerMock to do this, it can mock static methods, egzample: https://stackoverflow.com/questions/10583202/powermockito-mock-single-static-method-and-return-object – Krzysztof Cichocki Aug 08 '17 at 10:16
  • Thank you, I have also considered it, but I am not allowed to use PowerMock inside this project.. – Tomasz Bawor Aug 08 '17 at 10:22

1 Answers1

5

You have to record a behavior on the spy instance but you do it before creating the spy.

Here :

doReturn(mockA).when(testedClass).createA();
doReturn(mockB).when(testedClass).createB();
testedClass = Mockito.spy(new TestedClass());

testedClass is null when you record behaviors for mocks.

Reverse the order of these statements :

testedClass = Mockito.spy(new TestedClass());
doReturn(mockA).when(testedClass).createA();
doReturn(mockB).when(testedClass).createB();

Besides, this solution will not solve your root problem as the invoked no-arg constructor still depends of dependencies that you want to isolate but that are not yet at this time :

TestedClass() {
    a = createA();
    b = createB();
}

You should refactor a little TestedClass to allow to set dependencies.
You could for example overload the constructor :

TestedClass(A a, B b) {
    this.a = a;
    this.b = b;
}

In this way, you could mock dependencies of TestedClass :

@Before
public void setUp() {
    mockA = Mockito.mock(A.class);
    mockB = Mockito.mock(B.class);
    testedClass = new TestedClass(mockA, mockB);
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • Sorry, added wrong example. As I have edited problem is that when I use such construction, original method od TestClass are run, in unit test this is causing NullPointerException (There is some state inside) unabling me to finish initialization of spy. – Tomasz Bawor Aug 08 '17 at 10:11
  • 1
    Well. You should so add the stracktrace in your question and also the executed test . It may be helpful to understand the problem. – davidxxx Aug 08 '17 at 10:20
  • Already added example, I can not add real exception, but this should show what I am trying do. – Tomasz Bawor Aug 08 '17 at 10:30
  • I updated with a little change in the API. It should not be very intrusive in the legacy code. – davidxxx Aug 08 '17 at 10:39
  • I am aware that this is the best solution to increase independence of TestedClass, however I am required to do as small impact on this class as possible to prevent future errors, that is why I am trying some workarounds to avoid changing constructor signature . Thank you for your help :) – Tomasz Bawor Aug 08 '17 at 10:43
  • 2
    I don't propose you to change the signature but to overload it (a new constructor that lives with the actual one). Adding a new constructor/method doesn't break client code. – davidxxx Aug 08 '17 at 10:43
  • You are absolutely right, this is such small cosmetic change that I should be able to add without major issues – Tomasz Bawor Aug 08 '17 at 10:45