0

I have to test a method inside some class like this:

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

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

So, I have mocked SomeService class for returning my mock-object instead of original SomeService-object. I have done it via PowerMockito in every of my @Test method

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

And I have this annotation above my Test class:

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

It works fine if there is only one test, but if there is a few tests someServiceMock.getData() returns data from the very first test each time dispite the fact, that i mock it in every test with new data. I've tried to add annotation @PrepareForTest({aClassToTest.class, SomeService.class}) above every @Test method, but now I have an OutOfMemoryError after a few tests, and now it works only if I run whole Test class with all test methods, but if I run test methods separately I have No tests found for given includes error.

I have test class like this:

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

In this case, the return value of someService.getData() is always "123".

Steffen Moritz
  • 7,277
  • 11
  • 36
  • 55
fonzy
  • 53
  • 1
  • 8
  • Check wiki https://github.com/powermock/powermock/wiki/Mockito#introduction – Nkosi Jul 10 '19 at 11:28
  • You should probably post your full test class, it's not exactly clear what you're doing - sounds like the most simple case. Also, do you use Mockito or Powermock? – daniu Jul 10 '19 at 11:29
  • I use Mockito for regular mock objects, and PowerMock for mock creation of new object and mockStatic – fonzy Jul 10 '19 at 11:32
  • @fonzy Without a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) that clarifies your specific problem or additional details to highlight exactly what was done, it’s hard to reproduce the problem, allowing a better understanding of what is being asked. – Nkosi Jul 10 '19 at 11:34

3 Answers3

0

I don't know why this shouldn't work:

@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);
    }
}
daniu
  • 14,137
  • 4
  • 32
  • 53
  • I Have tried this, but SomeService creates an original object and calls original method now... – fonzy Jul 10 '19 at 12:38
  • @fonzy Do you have the correct annotations present in your test? You do need `RunWith(MockitoJunitRunner.class)` for the injection. Or explicitly call `Mockito.initMocks()`. – daniu Jul 10 '19 at 12:39
  • i have to mock some static methods btw, so i should use PowerMockito for this, and i couldn't use multiply annotations @RunWith(MockitoJUnitRunner.class) and @RunWith(PowerMockRunner.class) – fonzy Jul 10 '19 at 13:45
0

Use withNoArguments instead when configuring initialization. Also remove SomeService.class from @PrepareForTest

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

Note that you must prepare the class creating the new instance of SomeService for test, not the SomeService itself.

Reference How to mock construction of new objects


Separate to the issues being experienced with powermock, This demonstrates the problem with tightly coupling your code to dependencies that make it difficult to test in isolation.

A more SOLID approach would be to follow the explicit dependency principle using dependency inversion

public class aClassToTest{
    private SomeService someService;

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

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

This inverts the creation of the dependency to external of the target class and also states very clearly what is required by the class in order to perform its particular function.

This also allows for easier isolated unit tests on the target subject under test

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

In my opinion, just because PowerMock allows us the ability to mock the creation of object within other objects, does not mean that we should be encouraged to design tightly coupled classes that are difficult to maintain and test in isolation.

This answer is also meant to be an alternative to the current approach if a viable solution is not found.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Thank you! I am completely agree with you. But aClassToTest is not my class. It's have been written by one of my colleague, so i can't change it =/ – fonzy Jul 11 '19 at 07:13
-2

Thank you all. Problem solved by changing the design of a class, that should be tested.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
fonzy
  • 53
  • 1
  • 8