5

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.

2 Answers 2

8

Because when you use @MockBean annotation your context will be loaded for each test. Please refer to this github issue. The citation from this page :

The Spring test framework will cache an ApplicationContext whenever possible between test runs. In order to be cached, the context must have an exactly equivalent configuration. Whenever you use @MockBean, you are by definition changing the context configuration.

So when you use your mock bean in different tests - the context will be recreated each time for your test class. So if you for example have some beans that load data to DB on context creation - for example beans for flyway - they will be created each time context is recreated.

Sign up to request clarification or add additional context in comments.

Comments

4

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.