使用Mockito进行TestNG测试

9

我曾经用Mockito编写了一个JUnit的工作测试,现在想将其改为适用于TestNG,但奇怪的是,只有使用TestNG时才能通过一个测试。

我认为这与mocks的重置有关,但我已经尝试过调用Mockito.reset和使用BeforeMethod和BeforeClass以及不同的组合,但仍然只能通过一个测试。

我需要做什么才能让测试正常工作?

@BeforeClass
public void setUp() {       
    MockitoAnnotations.initMocks(this);                                
    mockMvc = MockMvcBuilders.standaloneSetup(calculatorController).build();
}

@AfterMethod
public void reset() {
    Mockito.reset(calculatorService);
}

@Test
public void addFunctionTest() throws Exception {             
    Assert.assertNotNull(calculatorController);
     Result expectedResult = new Result();
     expectedResult.setResult(10);

    when(calculatorService.add(anyInt(), anyInt())).thenReturn(expectedResult);                             

    mockMvc.perform(get("/calculator/add").accept(MediaType.APPLICATION_JSON_VALUE)
            .param("val1", "100")
            .param("val2", "100"))  
    .andExpect(content().contentType("application/json"))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$.result", equalTo(10)));    

    verify(calculatorService, times(1)).add(anyInt(), anyInt());
}   

@Test
public void subtractFunctionTest() throws Exception {            
    Assert.assertNotNull(calculatorController);
    Result expectedResult = new Result();
    expectedResult.setResult(90);

    when(calculatorService.subtract(anyInt(), anyInt())).thenReturn(expectedResult);                                

    mockMvc.perform(get("/calculator/subtract").accept(MediaType.APPLICATION_JSON_VALUE)
    .param("val1", "100")
    .param("val2", "10"))  
    .andExpect(content().contentType("application/json"))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$.result", equalTo(90)));    

    verify(calculatorService, times(1)).subtract(anyInt(), anyInt());
}

第二个测试总是在断言时失败,要么内容类型未设置,要么预期结果错误。
似乎第一个测试的响应在第二个测试中被评估,因此显然是错误的!
我知道控制器和服务按预期工作,并且使用jUnit运行的完全相同的测试实际上可以正常工作。
只有当我执行以下操作时,才能使测试正常运行:
 @BeforeGroups("subtract")
 public void reset() {      
    Mockito.reset(calculatorService);
    mockMvc =       MockMvcBuilders.standaloneSetup(calculatorController).build();
 }

 @Test(groups = "subtract")
 public void subtractFunctionTest() throws Exception {    
    System.out.println("***** IN METHOD *****");
    Assert.assertNotNull(calculatorController);
    Result expectedResult = new Result();
    expectedResult.setResult(90);

    when(calculatorService.subtract(anyInt(), anyInt())).thenReturn(expectedResult);                                

    //Perform HTTP Get for the homepage
    mockMvc.perform(get("/calculator/subtract").accept(MediaType.APPLICATION_JSON_VALUE)
    .param("val1", "100")
    .param("val2", "10"))  
    .andExpect(content().contentType("application/json"))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$.result", equalTo(90)));    

    //Verify that the service method was only called one time
    verify(calculatorService, times(1)).subtract(anyInt(), anyInt());
}

这意味着我需要为每个测试方法添加一个重置方法,然后需要为每个测试方法创建一个分组,这似乎不太正确。

你使用哪个库来进行断言(Result expectedResult = new Result())? - Eugen Martynov
你在每个方法之后都重置了模拟,但是你没有对mocMvc做任何操作。这会影响你的测试吗?你可以通过将@BeforeClass更改为@BeforeMethod来验证吗? - Eugen Martynov
嗨Eugen,我试着玩了一下,但没有任何组合可以工作。我将更新我的帖子,展示我已经成功的部分,但这将意味着大量的代码重复。 - berimbolo
库是Spring的MockMvcResultMatchers。 - berimbolo
3个回答

17

这些框架的行为存在差异:

  • JUnit为每个测试方法创建一个新的类实例,这意味着字段在测试之间不共享。
  • 但是TestNG只创建一个对象,因此字段中的状态在两个之间共享

对于Mockito,您需要在每个TestNG的测试方法之前初始化模拟(mock),以便状态不会在两个@Test之间共享:

@BeforeMethod
public void init() {
    MockitoAnnotations.initMocks(this);
}

对于JUnit而言,它可以直接使用,因为第二个@Test具有其自己的字段和模拟。


我已经尝试过这个方法,但它并没有起作用。调用的结果返回顺序错误。因此,如果我的 add 方法期望得到 5 的结果,而我的 subtract 方法期望得到 10 的结果,有时我的 add 测试会失败,并出现断言错误,指出它收到了 10,但期望的是 5。 - berimbolo
你确定你使用的是@BeforeMethod而不是@BeforeClass吗? - Stanislav Bashkyrtsev
尝试移除所有的Before/After代码,只需将字段标记为@Mock并插入我的代码 - 它必须能够正常工作。你不会并行运行测试,对吧? - Stanislav Bashkyrtsev
看起来测试是并行运行的,这是默认行为吗?因为我没有指定。 - berimbolo
3
对我而言,通过在@BeforeMethod中设置被注释为@InjectMocks的实例为null,然后在调用initMocks()之前进行操作可以起作用。请参见//see https://code.google.com/archive/p/mockito/issues/304。 - andy
显示剩余3条评论

4

我不确定这个答案是否适用于提问者的问题,因为模拟测试没有列出来。但是我想重新阐述一下andy在被接受的答案中的一个评论,它在我的同样的问题上帮了我。以下是更多细节和示例:


public class B {

private A a;

B (A a) {
    this.a = a
}

// ...

import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;


public class MyTest {

    @Mock
    A a;

    @InjectMocks
    B B;

    @BeforeMethod
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    void test1() {
        // ...
        // b.toString();
        // b.getA().toString();
        // a.toString();
    }

    @Test
    void test2() {
        // ...
        // b.toString();
        // b.getA().toString();
        // a.toString();
    }

    @AfterMethod
    public void reset()
    {
        // Solution:
        b = null;
    }
}

MockitoAnnotations的MockitoAnnotations.initMocks(this)只重新初始化模拟对象,就像Mockito.reset(a)只重置模拟对象一样。问题出在被注释了@InjectMocks的被测类(此处为B),这个类不会再次初始化。第一次测试时,它被初始化为A的模拟对象,A的模拟对象被重新初始化,但是B仍然保留着第一个版本的A。
解决方案是在任何合适的地方(@AfterMethod或在initMocks之前)手动重置被测类,例如b = null。然后Mockito也会重新初始化被测类B。

2

您还可以使用MockitoTestNGListener,它类似于JUnit MockitoJUnitRunnerMockitoExtension

代码示例:

import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import java.util.Map;

@Listeners(MockitoTestNGListener.class)
public class MyTest {

    @Mock
    Map map;

    @InjectMocks
    SomeType someType;

    @Test
    void test() {
        // ...
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接