I have two integration test classes. One of these classes depends on the bean that is talking to external service, so I need to mock this bean, and @MockBean
seems perfect for this. For injecting some seeds into DB I'm using flyway
's afterMigrate.sql
. So here is hot it looks like:
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
@Transactional
@Rollback
class FooTest {
@Autowired
private MyService myService;
}
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
@Transactional
@Rollback
class BarTest {
@MockBean
private ExternalService;
@Autowired
private MyService myService;
}
And afterMigrate.sql
:
INSERT INTO my_table (id, name) VALUES (1, 'John Doe')
The problem appeared when I annotate the ExternatService
as @MockBean
as now the afretMigrate.sql
runs twice and I'm getting the error:
java.lang.IllegalStateException: Failed to load ApplicationContext
....
Message : ERROR: duplicate key value violates unique constraint "my_table_pkey"
When I'm changing the @MockBean
to @Autowired
the error is gone and context is created without any problems. Also, tests run without problems if I run BarTest
separately.
This is not the expected behavior for @MockBean
as the documentation says:
Any existing single bean of the same type defined in the context will be replaced by the mock. If no existing bean is defined a new one will be added. Dependencies that are known to the application context but are not beans (such as those registered directly) will not be found and a mocked bean will be added to the context alongside the existing dependency.
It does not say that the context will be recreated.
Here is how I have resolved this issue (which I consider an issue).
Solution 1:
I have created a MockConfig
class with that should create one mock
for entire test suite:
@Configration
public class MockConfig {
@Bean
@Primary
public ExternalService externalService() {
return mock(ExternalService.class);
}
}
And in the test, I'm just autowiring the external service:
@Autowire
private ExternalService externalService;
But this solution has a problem, it will create a real bean then will override it with the mock bean. If your external service make a connection to the external resources on creation, and you don't need that then you will need another solution.
Solution 2:
Create a basic abstract class with @MockBean
in it:
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
@Transactional
@Rollback
public abstract class BaseIntegrationTest {
@MockBean
ExternalService externalService;
}
And extend the integration test from this base class:
class FooTest extends BaseIntegrationTest {
@Autowired
private MyService myService;
}
class BarTest extends BaseIntegrationTest {
@Autowired
private MyService myService;
}
Now the context won't refresh as it's always the same, and the real bean won't be created.