【问题标题】:Mock state is lost between JUnit test method invocations模拟状态在 JUnit 测试方法调用之间丢失
【发布时间】:2016-08-16 19:53:35
【问题描述】:

我不明白为什么test1() 会失败,尽管它与test2() 的作用相同。而另一种测试方法成功了……

我在assertTrue(str.equals("hello"));得到了NPE

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

下面的界面应该放在一个额外的文件中。否则它不能被 Mockito 模拟。

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

但是一旦我取消注释:

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

这两种方法都会成功。我看不出这条指令如何影响str 字段。看起来strtest1()test2() 的调用之间设置为null。但为什么和在哪里?据我所知,我不必仅仅为了保留某些字段的当前值而调用setUp()SomeTest(包括str)的状态不应在 JUnit 中的测试方法调用之间丢失。

如何解释?

【问题讨论】:

  • 为什么要混淆 JUnit 版本? junit.framework.TestCaseorg.junit.*?
  • @SotiriosDelimanolis 我只是将它限制在一个 - junit.framework.TestCase 具有相同的结果。

标签: java testing junit mocking mockito


【解决方案1】:

您正在混合使用 JUnit 3 和 JUnit 4。不要。摆脱

extends TestCase {

这可能会让您的测试运行程序使用 JUnit 3 来运行测试。

JUnit 总是创建一个新的测试类实例来运行每个测试方法。

在 JUnit 3 中,运行程序在执行任何方法之前准备所有实例。在你的情况下,执行基本上是这样的

  1. 实例化SomeTest,实例A
  2. 在实例 A 上调用构造函数
  3. 实例化ToMock,实例B,绑定实例A,并在UnderTest单例中注册
  4. 实例化SomeTest,实例C
  5. 在实例 C 上调用构造函数
  6. 实例化ToMock,实例D,绑定实例C,并在UnderTest单例中注册,覆盖之前的内容
  7. 在实例 A 上调用 test1
  8. ToMock 实例上调用set B
  9. ToMock 实例D 上调用doSomething,因为这是存储在UnderTest 单例中的那个,通过getInstance 检索

ToMock 实例 D 尚未调用 set。因此,对应的SomeTest 实例Cstr 字段仍然是null


在 JUnit 4 中,我们交替创建实例和运行相应的测试。在您的测试用例中,UnderTest 单例不会被错误的 ToMock 实例“污染”。

【讨论】:

  • 我在构造函数和两种测试方法中都设置了断点。控制流程如您所描述。但这不是愚蠢和反直觉的吗?我的意思是您通常希望构造函数被调用一次。我可能一直在使用 JUnit 4。我从来没有想过版本之间的区别。不幸的是,项目中使用了 JUnit 3,所以我不得不使用它。切换可能是可能的,但不会那么快。
  • @ka3ak 这是 JUnit 的设计决定。请参阅 Martin Fowler 关于它的文章,here。在您的情况下,不要在构造函数中将ToMock 提供给您的单例,而是在每个测试或@Before 中进行。
【解决方案2】:

对设置方法 (@Before) 和 @After 注释方法的任何更新都将在方法调用之间持续存在。

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

但是,每次调用测试方法之前/之后都会调用该调用。如果这对您来说还不够,请考虑使用 @BeforeClass 注释。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多