【问题标题】:How to mock creation new object inside some service?如何在某些服务中模拟创建新对象?
【发布时间】:2019-07-10 11:23:45
【问题描述】:

我必须像这样在某个类中测试一个方法:

public class aClassToTest{
  private SomeService someService = new SomeService();

  public String methodToTest(){
  String data = someService.getData();
  //....
 }
}

所以,我模拟了 SomeService 类以返回我的模拟对象而不是原始的 SomeService 对象。我在每个@Test 方法中都通过 PowerMockito 完成了这项工作

SomeService someServiceMock = mock(SomeService.class); 
when(someServiceMock.getData().thenReturn(Data myMockedData)
PowerMockito.whenNew(SomeService.class).withAnyArguments().thenReturn(someServiceMock);

我的测试类上方有这个注释:

@PrepareForTest({aClassToTest.class, SomeService.class})

如果只有一个测试它工作正常,但如果有几个测试someServiceMock.getData() 每次都会返回第一个测试的数据,尽管我在每次测试中都使用新数据模拟它。我尝试在每个@Test 方法上方添加注释@PrepareForTest({aClassToTest.class, SomeService.class}),但现在经过几次测试后我有了OutOfMemoryError,现在它只有在我使用所有测试方法运行整个Test 类时才有效,但如果我运行分别测试方法我有No tests found for given includes 错误。

我有这样的测试类:

@RunWith(PowerMock.class)
@PrepareForTest({aClassToTest.class, SomeService.class})
public class TestClass{

private void doMockSomeService(String testData){
  SomeService someServiceMock = mock(SomeService.class); 
  when(someServiceMock.getData().thenReturn(testData);
  PowerMockito.whenNew(SomeService.class).withAnyArguments().thenReturn(someServiceMock);
 }

  @Test
  public void testCase1(){
  String expectedResult = "expectedResult1";
  doMockSomeService("123");
  ClassToTest classToTest = new ClassToTest();
  String result = classToTest.methodToTest();
  assertEquals(result, expectedResult);
  }

 @Test
  public void testCase2(){
  String expectedResult = "expectedResult2";
  doMockSomeService("456");
  ClassToTest classToTest = new ClassToTest();
  String result = classToTest.methodToTest();
  assertEquals(result, expectedResult);
  }
}

在这种情况下,someService.getData() 的返回值总是“123”。

【问题讨论】:

  • 您可能应该发布完整的测试课程,但不清楚您在做什么 - 听起来像是最简单的情况。另外,您使用 Mockito 还是 Powermock?
  • 我将 Mockito 用于常规模拟对象,使用 PowerMock 模拟创建新对象和模拟静态
  • @fonzy 如果没有 minimal reproducible example 来澄清您的具体问题或其他详细信息以准确突出所做的工作,则很难重现问题,从而更好地理解所询问的内容。

标签: java junit mockito powermock powermockito


【解决方案1】:

我不知道为什么这不起作用:

@RunWith(MockitoJUnitRunner.class)
class MyTest {
    @Mock private SomeService service;
    @InjectMocks private aClassToTest;

    @Before
    public void initData() {
        when(service.getData()).thenReturn(data);
    }

    @Test
    public void test() {
        mockData("expected");

        String result = aClassToTest.methodToTest();
        verify(service).getData(); // method was called

        assertEquals("expected", result);
    }

    private void mockData(String str) {
        when(service.getData()).thenReturn(str);
    }
}

【讨论】:

  • 我已经尝试过了,但是 SomeService 现在创建了一个原始对象并调用了原始方法...
  • @fonzy 你的测试中有正确的注释吗?您确实需要RunWith(MockitoJunitRunner.class) 进行注射。或者显式调用Mockito.initMocks()
  • 顺便说一句,我必须模拟一些静态方法,所以我应该为此使用 PowerMockito,而且我不能使用多重注释 @RunWith(MockitoJUnitRunner.class) 和 @RunWith(PowerMockRunner.class)跨度>
【解决方案2】:

在配置初始化时使用withNoArguments。同时从@PrepareForTest中删除SomeService.class

@RunWith(PowerMockRunner.class)
@PrepareForTest(aClassToTest.class)
public class TestClass {

    private void doMockSomeService(String testData){
        SomeService someServiceMock = PowerMockito.mock(SomeService.class); 
        PowerMockito.when(someServiceMock.getData().thenReturn(testData);
        PowerMockito.whenNew(SomeService.class).withNoArguments()
            .thenReturn(someServiceMock);
    }

    @Test
    public void testCase1() {
        //Arrange
        String expectedResult = "expectedResult1";
        doMockSomeService("123");
        ClassToTest classToTest = new ClassToTest();
        //Act
        String result = classToTest.methodToTest();
        //Assert
        assertEquals(result, expectedResult);
    }

    @Test
    public void testCase2() {
        //Arrange
        String expectedResult = "expectedResult2";
        doMockSomeService("456");
        ClassToTest classToTest = new ClassToTest();
        //Act
        String result = classToTest.methodToTest();
        //Assert
        assertEquals(result, expectedResult);
    }
}

请注意,您必须准备创建 SomeService 新实例的类以进行测试,而不是 SomeService 本身。

参考How to mock construction of new objects


与使用 powermock 时遇到的问题分开,这说明了将代码与依赖项紧密耦合的问题,这使得孤立地测试变得困难。

更可靠的方法是使用依赖倒置遵循显式依赖原则

public class aClassToTest{
    private SomeService someService;

    @Inject    
    public aClassToTest(SomeService someService) {
        this.someService = someService;
    }

    public String methodToTest(){
        String data = someService.getData();
        //....
    }
}

这将创建对目标类外部的依赖项进行了反转,并且还非常清楚地说明了该类执行其特定功能所需的内容。

这还允许对被测目标主题进行更轻松的独立单元测试

@RunWith(MockitoJUnitRunner.class)
public class TestClass {

    private someServiceMock doMockSomeService(String testData){
        someServiceMock = mock(SomeService.class); 
        when(someServiceMock.getData().thenReturn(testData);
        return someServiceMock;
    }

    @Test
    public void testCase1() {
        //Arrange
        String expected = "expectedResult1";
        ClassToTest classToTest = new ClassToTest(doMockSomeService("123"));
        //Act
        String actual = classToTest.methodToTest();
        //Assert
        assertEquals(expected, actual);
    }

    @Test
    public void testCase2() {
        //Arrange
        String expected = "expectedResult2";
        ClassToTest classToTest = new ClassToTest(doMockSomeService("456"));
        //Act
        String actual = classToTest.methodToTest();
        //Assert
        assertEquals(expected, actual);
    }
}

在我看来,仅仅因为 PowerMock 允许我们模拟在其他对象中创建对象的能力,并不意味着我们应该被鼓励设计难以单独维护和测试的紧密耦合类。

如果找不到可行的解决方案,此答案也可以作为当前方法的替代方案。

【讨论】:

  • 谢谢!我完全同意你的看法。但是 aClassToTest 不是我的课。它是我的一位同事写的,所以我无法更改它=/
【解决方案3】:

谢谢大家。通过更改类的设计解决了问题,应该对其进行测试。

【讨论】:

  • 把设计改成什么?
猜你喜欢
  • 2017-04-07
  • 2019-12-24
  • 2020-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-09
相关资源
最近更新 更多