Home » Java » Java/Spring: initMocks a class which got @Value from yml file-Exceptionshub

Java/Spring: initMocks a class which got @Value from yml file-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

I am writing unit tests on a Service class like below :

@Component
@Profile({"default", "dev"})
public class MyService {
    @Value("${my.property}")
    private String property;

    private OtherService otherService;

    public void MyMethod() {
        String myVar = otherService.method();

        return String.format("MyVar %s & MyProperty %s", myVar, property);
    }
}

My current test class for this test is like this:

@ActiveProfiles("dev")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyApplication.class)
public class MyServiceTest {
    @Mock
    private OtherService otherService;

    @InjectMocks
    private MyService myService;

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

        when(otherService.method()).thenReturn("a string");
    }

    @Test
    public void shouldReturnException() {
        final Exception exception = assertThrows(ApiErrorException.class,
        () -> myService.myMethod(var));

    assertThat(exception).hasMessage("here the message");

    verify(otherService, never()).method();
    }
}

With this two classes, I have an application.yml & application-dev.yml to set my.property.
I want to get the property from the application-dev file during my tests execution.
But, with @InjectMocks, property is null. Whereas, using @Autowired in place of/with @InjectMocks, the property variable is set with the value present in file.

Problem, using Autowired with/in place of InjectMock results in the otherService variable being initialized, so no mock is created.

How can I still use Mockito, while having the property variable set with the value in the file?
I saw about ReflectionTestUtils.setField, but using it mean having no use of a yml file (which i am not fan).

Have a nice day


With the help of @Deadpool, the tests can use the values written in the application.yml file.
But, using @MockBean and @Autowired, the tests get a behavior I do not understand.

Example:
I test that a method return an Exception, and I verify that others methods are not called after the exception was catch: verify(otherService, never()).otherMethod();
Writing this line returns the following error org.mockito.exceptions.verification.NeverWantedButInvoked.

The initial exception is correctly caught, but the test does not seem to acknowledge that no other services must be called.

How to&Answers:

@SpringBootTest is used for integration testing which loads the ApplicationContext that will be utilized for test environment

The @SpringBootTest annotation can be used when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests.

Since you are generating integration environment using @SpringBootTest you need to mock the bean using @MockBean annotation

@ActiveProfiles("dev")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyApplication.class)
public class MyServiceTest {

   @MockBean
   private OtherService otherService;

   @Autowire
   private MyService myService;

   @BeforeEach
   public void init() {

    when(otherService.method()).thenReturn("a string");
   }
}

Answer:

I suggest you change your MyService class to accept the OtherService either via the constructor or via setter. Something like this:

@Component
@Profile({"default", "dev"})
public class MyService {
    @Value("${my.property}")
    private String property;

    private OtherService otherService;

    public MyService(OtherService otherService) {
        this.otherService = otherService
    }

    public void MyMethod() {
        String myVar = otherService.method();

        return String.format("MyVar %s & MyProperty %s", myVar, property);
    }
}

And then you do your test like this:

@ActiveProfiles("dev")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyApplication.class)
public class MyServiceTest {
    @Mock
    private OtherService otherService;

    @InjectMocks
    @Autowired
    private MyService myService;

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

        when(otherService.method()).thenReturn("a string");
    }
}