Search code examples
spring-bootreflectionmockitojunit5spring-boot-test

Springboot & Mockito - Using ReflectionTestUtils.setField calling method twice


I want to inject value to a private field annotated with @Value in my Junit5 unit test.

@Value("$(invoice.maxRetry)")
private maxRetry;

I referred this and used ReflectionTestUtils.setField which solved my problem by injecting a value but failed when verifying the no. of times the method get called.

MyClass:

    public class MessageProcessor {
         
    @Value("$(invoice.maxRetry)")
        private maxRetry;
    
    protected void handleMessage() {
            if(retry > maxRetry) {
                kafkaTemplate.sendMessage(msg);
       }
}

TestClass:

@ExtendWith(MockitoExtension.class)
public class MessageProcessorTest {

@Mock
private kafkaTemplate kafkaTemplate;

@Mock
private MessageProcessor messageProcessor

@Test
test() {
     ReflectionTestUtils.setField(messageProcessor, "maxRetry", "5");
     doNothing().when(kafkaTemplate).sendMessage(any());
     messageProcessor.handleMessage();
     verify(kafkaTemplate).sendMessage(any());
  }
}

Error running above test

org.mockito.exceptions.verification.TooManyActualInvocations: 
kafkaTemplate.sendMessage(<any>);
Wanted 1 time:
But was 2

I want kafkaTemplate.sendMessage(); to be called only once but getting called twice after adding ReflectionTestUtils.

Need advice on how to fix this.


Solution

  • You can avoid the usage of ReflectionTestUtils by slightly refactoring your class and favoring construct injection:

    public class MessageProcessor {
         
        private String maxRetry;
        private KafkaTemplate template;
    
        // ... any further fields
    
        public class MessageProcessor(@Value("$(invoice.maxRetry)") String maxRetry, KafkaTemplate kafkaTemplate) {
           this.maxRetry = maxRetry;
           this.kafkaTemplate = kafkaTemplate;
        }
    
     }
    

    Within your test you can then control the value of maxRetry by creating an instance of your class under test (MessageProcessor) manually.

    @ExtendWith(MockitoExtension.class)
    public MessageProcessorTest {
    
      @Mock 
      private KafkaTemplate kafkaTemplate;
    
      private MessageProcessor messageProcessor;
    
      @BeforeEach
      void setUp() {
        this.messageProcessor = new MessageProcessor("42", kafkaTemplate);
    
      }
    
    }
    

    ... and then only rely on JUnit and Mocktio which should help you add the verification.