0

In an integration test (@SpringBootTest) class I declare a spyBean:

@SpyBean
private Environment environment;

In a test I try to mock one method:

doReturn(true).when(environment).acceptsProfiles(Profiles.of("prod"));

However, I get the following error:

org.mockito.exceptions.misusing.NotAMockException: 
Argument passed to when() is not a mock!
Example of correct stubbing:
    doThrow(new RuntimeException()).when(mock).someMethod();

How can I properly mock the method?

Greg
  • 1,227
  • 5
  • 23
  • 52

1 Answers1

2

Environment is part of the essential configuration used to create Spring context while the application is started. The environment object is created before the context is prepared and only later registered as a bean within the context to enable injection. Because of that it cannot be mocked or spied on, similarly to ApplicationContext or BeanFactory, which are basic Spring context building blocks as well.

You can verify that yourself, using a debugger and the Spring Boot open-sourced code - look specifically at this part (run method in the SpringApplication class). The Environment object is prepared before the context is created and the mock/spy beans are created and injected during context refresh after its creation. To verify that you could simply create a class, mark it with the @SpyBean annotation in a test, set a breakpoint in the constructor and run the test with debugging. Whether Spring testing library should inform about the incorrect usage of @MockBean/@SpyBean on the interfaces/classes that cannot be mocked or spied on is a different matter and you could ask that question or propose adding that in an appropriate GitHub repository.

Regarding the solution to the problem (I think) you're trying to solve: it looks like you could use the @ActiveProfiles annotation to enable a specific profile in your test class or use any other way of setting an active Spring profile.

Jonasz
  • 1,617
  • 1
  • 13
  • 19
  • thank you for your answer! My problem is, I di not actually want to use settings from the profile in the test, I only want to verify that specific functionality works only if a profile is active. – Greg Jun 04 '23 at 14:17
  • Is the functionality based on choosing different beans for injection depending on the profile? This knowledge is required to solve your problem fully, because beans creation takes place on context initialization/refresh, which occurs before running the test case (or even whole test class, depending on the configuration). – Jonasz Jun 05 '23 at 07:08
  • well there is something in the code like if (environment.acceptsProfiles(...)) {someBean.doThis();} else {throw Exception(...)}. I need a way to verify the behaviour in an integrative test without actually using the settings from the "prod" profile. – Greg Jun 05 '23 at 08:51
  • 1
    If you'd like to test just the `if` (BTW you should consider using different strategies as beans created based on a profile and injected depending on the profile value), it would be best to just stick to a unit test (without Spring context) with mocked `environment`. If it is an integration test, then using a profile has its other consequences like settings and beans. To work around that you could create an additional profile like `test` and skip the settings/beans you do not need if it's active and activate both `prod` and `test` profiles in the test. – Jonasz Jun 05 '23 at 09:20