@MockBean似乎重新运行上下文创建,并且afterMigrate.sql后失败

时间:2019-05-28 15:46:25

标签: java spring mockito flyway spring-test

我有两个集成测试类。这些类之一取决于与外部服务通信的bean,因此我需要模拟此bean,@MockBean似乎很完美。为了向DB中注入一些种子,我使用了flyway的{​​{1}}。所以这里很热,就像:

afterMigrate.sql

还有@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; }

afterMigrate.sql

当我将INSERT INTO my_table (id, name) VALUES (1, 'John Doe') 注释为ExternatService时出现了问题,因为现在@MockBean运行两次并且出现错误:

afretMigrate.sql

当我将java.lang.IllegalStateException: Failed to load ApplicationContext .... Message : ERROR: duplicate key value violates unique constraint "my_table_pkey" 更改为@MockBean时,错误消失了,创建了上下文,没有任何问题。另外,如果我分别运行@Autowired,则测试运行不会出现问题。 如文档所述,这不是BarTest的预期行为:

  

在上下文中定义的任何相同类型的现有单个bean都会   被模拟代替。如果没有定义现有的bean,那么将有一个新的bean   被添加。应用程序上下文已知的相关性,但   不是bean(例如直接注册的bean)   并且将模拟的bean与现有的bean一起添加到上下文中   依赖性。

它并不表示将重新创建上下文。

2 个答案:

答案 0 :(得分:4)

由于使用@MockBean批注时,将为每个测试加载上下文。请参阅this github issue。此页面的引用:

  

Spring测试框架将在测试运行之间尽可能地缓存ApplicationContext。为了进行缓存,上下文必须具有完全等效的配置。每当使用@MockBean时,根据定义,您就是在更改上下文配置。

因此,当您在不同的测试中使用模拟bean时,每次都会为您的测试类重新创建上下文。因此,例如,如果您有一些在上下文创建时将数据加载到DB的bean(例如,用于flyway的bean),则每次重新创建上下文时都会创建它们。

答案 1 :(得分:1)

这是我解决此问题(我认为是问题)的方式。

解决方案1: 我创建了一个<template> <ul class="mt-1 p-0 list-unstyled clearfix" v-if="showData && data.data && data.data.length"> <li v-for="(singleResult, index) in data.data" ref="items" class="list-group-item mb-2"> <note-single-list-item-deleted v-else-if="singleResult.contentType === 'notes'" :note="singleResult" @restore="restore($event, index)"></note-single-list-item-deleted> </li> </ul> </template> <script> import noteSingleListItemDeleted from './content-type-comps-deleted/note-single-list-item-deleted'; export default { components : { 'note-single-list-item-deleted' : noteSingleListItemDeleted, }, data() { return { showLoadingAnimation: false, showData: false, data: {}, dataRequestSent : false, contentType: "notes", restoreRequestSent : false, } }, methods: { getRestoredLinkTemplate(contentType, id) { let contentTypeLink; let contentTypeToDisplay; if(contentType === 'notes') { contentTypeLink = '/note/' + id; contentTypeToDisplay = 'note' } // this give error --------------- let temp = new Vue({ template: '<div><router-link :to="link"><p class="m-0 p-0">Go to restored {{ contentTypeToDisplay }}</p></router-link></div>', data () { return { link: contentTypeLink, contentTypeToDisplay : contentTypeToDisplay } } }); // this works ------------------ // let temp = new Vue({ // template: '<div class="m-0 p-0"><a :href="link" target="_blank"><p class="m-0 p-0">Go to restored {{ contentTypeToDisplay }}</p></a></div>', // data () { // return { // link: contentTypeLink, // contentTypeToDisplay : contentTypeToDisplay // } // } // }); return temp; }, restore(item, index){ if(this.restoreRequestSent) { return; } this.restoreRequestSent = true; axios.post('/trash/restore', item).then((response) => { let newLink = this.getRestoredLinkTemplate(response.data.content_type, response.data.id).$mount().$el; this.$refs.items[index].innerHTML = ""; this.$refs.items[index].appendChild(newLink); this.restoreRequestSent = false; }); }, }, } </script> 类,应该为整个测试套件创建一个MockConfig

mock

在测试中,我只是自动装配外部服务:

@Configration
public class MockConfig {

  @Bean
  @Primary
  public ExternalService externalService() {
    return mock(ExternalService.class);
  }
}

但是此解决方案有一个问题,它将创建一个真实的bean,然后将其与模拟bean覆盖。如果您的外部服务在创建时就连接到外部资源,而您不需要这样做,那么您将需要另一种解决方案。

解决方案2: 创建一个包含@Autowire private ExternalService externalService; 的基本抽象类:

@MockBean

并从此基类扩展集成测试:

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
@Transactional
@Rollback
public abstract class BaseIntegrationTest {
  @MockBean
  ExternalService externalService;
}

现在,上下文将始终保持不变,因此不会刷新,并且不会创建真正的bean。