ka3ak
ka3ak

Reputation: 3191

Mock state is lost between JUnit test method invocations

I don't understand why test1() fails although it does the same as test2(). And the other test method succeeds...

I get NPE in assertTrue(str.equals("hello"));

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import junit.framework.TestCase;

class UnderTest {
    private static UnderTest instance;
    private ToMock toMock;

    public void doSomething() {
        String str = toMock.get();
        assertTrue(str.equals("hello"));
    }

    public static UnderTest getInstance() {
        if (instance == null) {
            instance = new UnderTest();
        }
        return instance;
    }

    public void set(ToMock toMock) {
        this.toMock = toMock;
    }

    public ToMock get() {
        return toMock;
    }
}

public class SomeTest extends TestCase {
    private ToMock toMock;
    private UnderTest underTest;
    private String str;

    public SomeTest() {
        toMock = mock(ToMock.class);

        doAnswer(new Answer<Void>() {
            public Void answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                str = (String) args[0];
                return null;
            }
        }).when(toMock).set((String) org.mockito.Mockito.any());

        when(toMock.get()).thenAnswer(new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocation) throws Throwable {
                return str;
            }
        });

        UnderTest.getInstance().set(toMock);
    }

    @Before
    public void setUp() throws Exception {
        //      UnderTest.getInstance().set(toMock);
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void test1() {
        toMock.set("hello");
        UnderTest.getInstance().doSomething();
    }

    @Test
    public void test2() {
        toMock.set("hello");
        UnderTest.getInstance().doSomething();
    }
}

The interface below should be placed in an extra file. Otherwise it can't be mocked by Mockito.

public interface ToMock {
    void set(String str);
    String get();
}

But as soon as I uncomment:

@Before
public void setUp() throws Exception {
    //      UnderTest.getInstance().set(toMock);
}

both methods will succeed. I don't see how this instruction affects the str field. It looks like str is set to null between the invocations of test1() and test2(). But why and where? As far as I know I don't have to call setUp() just to keep the current values of some fields. The state of SomeTest (str included) shouldn't be lost between the test method invocations in JUnit.

How can it be explained?

Upvotes: 4

Views: 821

Answers (2)

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 279990

You're mixing JUnit 3 and JUnit 4. Don't. Get rid of the

extends TestCase {

which presumably makes your test runner use JUnit 3 for running the tests.

JUnit always create a new instance of the test class to run each test method.

In JUnit 3, the runner prepares all instances before executing any of the methods. In your case, execution basically goes like this

  1. Instantiate SomeTest, instance A
  2. Invoke constructor on instance A
  3. Instantiate ToMock, instance B, bound to instance A, and register it in UnderTest singleton
  4. Instantiate SomeTest, instance C
  5. Invoke constructor on instance C
  6. Instantiate ToMock, instance D, bound to instance C, and register it in UnderTest singleton, overwriting what was there before
  7. Invoke test1 on instance A
  8. Invoke set on ToMock instance B
  9. Invoke doSomething on ToMock instance D because that's the one stored in UnderTest singleton, retrieved through getInstance

That ToMock instance D hasn't had set called on it yet. The corresponding SomeTest instance C's str field is therefore still null.


With JUnit 4, we alternate creating an instance with running the corresponding test. In your test case, the UnderTest singleton wouldn't be "poluted" with the wrong ToMock instance.

Upvotes: 5

Amit Mahajan
Amit Mahajan

Reputation: 915

Any updates to setup method (@Before) and @After annotated methods will persist between method calls.

 @Before
public void executedBeforeEach() {
....
}

The call however will be invoked each time before/after test method is called. If this is not sufficient for you then consider using @BeforeClass annotations.

Upvotes: 1

Related Questions