1

I'm using Junit5 for test my spring code.

In the test code, I mock SetRepository, to make the getSize() method return value 40. Then I stubbed ListService's findSetSize() method and made it return value 30.

Then print the getSize() method value. Expected 40, but 30 printed.

It looks likegetSize() was overwritten while stubbing, but I'd like to know why.

// ListService.java
@Service
@RequiredArgsConstructor
public class ListService {

    private final SetRepository setRepository;

    public int findSetSize() {
        int size = setRepository.getSize();
        System.out.println(size);  // first result: 40, second result: 30
        return size;
    }

    public void saveToSet(int num) {
        setRepository.saveNum(num);
    }

}

// SetRepository.java
@Component
public class SetRepository {

    Set<Integer> set = new HashSet<>();

    public int getSize() {
        return set.size();
    }

    public void saveNum(int num) {
        set.add(num);
    }
}

// Test code for ListService
@ExtendWith(MockitoExtension.class)
public class ListServiceWithMockTest {

    @InjectMocks
    @Spy
    ListService subject;


    @Mock
    SetRepository setRepository;

    @Test
    @DisplayName("can get Set size")
    public void findSetSizeTest() {
        //given
        when(setRepository.getSize()).thenReturn(40);
        when(subject.findSetSize()).thenReturn(30);

        //when
        subject.saveToSet(100);

        //then
        System.out.println(setRepository.getSize());  // expected:40, result: 30
        assertEquals(30, subject.findSetSize());
    }

}

Here is my code. I know I shouldn't do stubbing like this, but I'm curious why the value comes out like this. Thank U!!

rabong
  • 139
  • 1
  • 7

1 Answers1

1

In your example, you want to mock a method within the same class. But this is not possible in the way you did. You create a real object using @InjectMocks. You need to use Mockito.spy() to partially mock this.

If you want to get the results you expect correctly and really mock your findSetSize() method, you should change your test method to follow:

@Test
@DisplayName("can get Set size")
void findSetSizeTest() {
    //given
    ListService spyService = spy(subject);
    when(setRepository.getSize()).thenReturn(40);
    doReturn(30).when(spyService).findSetSize();

    //when
    subject.saveToSet(100);

    //then
    System.out.println(setRepository.getSize());  // expected:40, result: 30
    Assertions.assertEquals(30, spyService.findSetSize());
}

Here you have to understand that mocking would work only when you call it on the instance of spy object. It's wrapped by a Mockito proxy which catches your call, and if you have overriden some method, it will call your new implementation instead of the original one.

Also the documentation related to spy says:

You can create spies of real objects. When you use the spy then the real methods are called (unless a method was stubbed).

You can review the answers here and here regarding the differences between when().thenReturn() and doReturn().when().

fatih
  • 1,285
  • 11
  • 27
  • Thanks for answering question. I add @Spy annotation in ListService. I know that setRepository.getSize() value doesn't change when using **doReturn**. But when I use **when.thenReturn** the value of setRepository.getSize() changes. Can you explain why this happens? – rabong Dec 28 '22 at 01:13
  • I updated the answer regarding the differences between the two. – fatih Dec 28 '22 at 05:36
  • I saw the answer. Thanks!! But I still don't know why this result comes out. – rabong Dec 30 '22 at 05:16