我有两个集成测试类。这些类之一取决于与外部服务通信的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一起添加到上下文中 依赖性。
它并不表示将重新创建上下文。
答案 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。