单元测试环境下Spring beans重新定义
-
05-09-2019 - |
题
我们使用 Spring 来实现我的应用程序目的,并使用 Spring 测试框架来进行单元测试。不过我们有一个小问题:应用程序代码从类路径中的位置列表(xml 文件)加载 Spring 应用程序上下文。但是当我们运行单元测试时,我们希望一些 Spring bean 是模拟的,而不是成熟的实现类。此外,对于某些单元测试,我们希望某些 bean 成为模拟,而对于其他单元测试,我们希望其他 bean 成为模拟,因为我们正在测试应用程序的不同层。
所有这些意味着我想重新定义应用程序上下文的特定 bean 并在需要时刷新上下文。在执行此操作时,我只想重新定义位于一个(或多个)原始 xml beans 定义文件中的一小部分 beans。我找不到简单的方法来做到这一点。人们总是认为 Spring 是一个单元测试友好的框架,所以我一定在这里遗漏了一些东西。
您有什么想法如何去做吗?
谢谢。
解决方案
我建议一个自定义的TestClass和一些简单的规则用于弹簧bean.xml的
的位置@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath*:spring/*.xml",
"classpath*:spring/persistence/*.xml",
"classpath*:spring/mock/*.xml"})
@Transactional
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DirtiesContextTestExecutionListener.class})
public abstract class AbstractHibernateTests implements ApplicationContextAware
{
/**
* Logger for Subclasses.
*/
protected final Logger LOG = LoggerFactory.getLogger(getClass());
/**
* The {@link ApplicationContext} that was injected into this test instance
* via {@link #setApplicationContext(ApplicationContext)}.
*/
protected ApplicationContext applicationContext;
/**
* Set the {@link ApplicationContext} to be used by this test instance,
* provided via {@link ApplicationContextAware} semantics.
*/
@Override
public final void setApplicationContext(
final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
如果有模拟bean.xml在指定位置,他们将覆盖在“正常”位置的所有“真实” bean.xml - 你的正常的位置可能会有所不同。
但是......我绝不会混合模拟和非模拟豆很难跟踪问题,当应用程序长大。
其他提示
spring 被描述为测试友好的原因之一是因为它可能很容易 新的 或单元测试中的模拟内容。
或者,我们使用了以下设置并取得了巨大成功,我认为它非常接近您想要的,我会 强烈地 推荐一下:
对于在不同上下文中需要不同实现的所有 bean,请切换到基于注释的连接。您可以让其他人保持原样。
实现以下一组注释
<context:component-scan base-package="com.foobar">
<context:include-filter type="annotation" expression="com.foobar.annotations.StubRepository"/>
<context:include-filter type="annotation" expression="com.foobar.annotations.TestScopedComponent"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
然后你注释你的 居住 使用 @Repository 实现,您的 存根 使用 @StubRepository 实现,任何应该出现在单元测试装置中的代码只能使用 @TestScopedComponent。您可能会遇到需要更多注释的情况,但这是一个很好的开始。
如果您有很多 spring.xml,您可能需要创建一些新的 spring xml 文件,这些文件基本上只包含组件扫描定义。您通常只需将这些文件附加到常规 @ContextConfiguration 列表中。这样做的原因是因为您经常会得到不同的上下文扫描配置(相信我,您 将要 如果您正在进行 Web 测试,则至少再添加 1 个注释,这将产生 4 个相关组合)
然后你基本上使用
@ContextConfiguration(locations = { "classpath:/path/to/root-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
请注意,此设置不会 不是 允许您拥有存根/实时数据的交替组合。我们尝试过这个,我认为这会导致一团糟,我不会推荐任何人;)我们要么通过全套存根或全套现场服务进行有线连接。
当测试 gui 附近的依赖项通常相当大的东西时,我们主要使用自动连接的存根依赖项。在代码的更干净的区域,我们使用更常规的单元测试。
在我们的系统中,我们有以下用于组件扫描的 xml 文件:
- 用于常规网页制作
- 仅使用存根启动网络
- 用于集成测试(在junit中)
- 用于单元测试(在junit中)
- 用于 Selenium Web 测试(在 junit 中)
这意味着我们总共有 5 种不同的系统范围配置可供我们启动应用程序。因为我们只使用注释,所以 spring 足够快,甚至可以自动连接那些我们想要连接的单元测试。我知道这很不传统,但这真的很棒。
集成测试通过完整的实时设置运行,并且有一两次我决定进行 真的 务实,想要有 5 个带电线路和一个模拟:
public class HybridTest {
@Autowired
MyTestSubject myTestSubject;
@Test
public void testWith5LiveServicesAndOneMock(){
MyServiceLive service = myTestSubject.getMyService();
try {
MyService mock = EasyMock.create(...)
myTestSubject.setMyService( mock);
.. do funky test with lots of live but one mock object
} finally {
myTestSubject.setMyService( service);
}
}
}
我知道测试纯粹主义者会为此对我全力以赴。但有时,这只是一个非常务实的解决方案,但事实证明它非常优雅,而替代方案却非常丑陋。同样,它通常位于那些靠近 gui 的区域。
这为我节省了很多时间。你只需要使用
@Mock
SomeClass mockedSomeClass
@InjectMock
ClassUsingSomeClass service
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
和你所有的问题都解决了。会的Mockito更换春天依赖注入一个模拟。我只是用它自己和它的伟大工程。
有在这里列出了一些非常复杂和强大的解决方案。
但有一个远远简单方法来完成斯塔斯已要求,不涉及修改比测试方法的一行代码的任何其他。它适用于单元测试和Spring集成测试一样,对于自动装配Autowired依赖性,private和protected领域。
下面,它是:
junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject);
您也可以编写单元测试不需要任何查找可言:
@ContextConfiguration(locations = { "classpath:/path/to/test-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class MyBeanTest {
@Autowired
private MyBean myBean; // the component under test
@Test
public void testMyBean() {
...
}
}
这给出了一个简单的方法来混合,并用测试配置文件匹配真实配置文件。
例如,使用休眠时,我得着的sessionFactory豆在一个配置文件(在测试和主要的应用程序都被使用),并且通过在另一个配置文件的dataSource豆具有(一个可能使用DriverManagerDataSource和至在内存中的分贝,其他可能使用一个JNDI查找)。
但是,绝对采取 @克莱图斯的理会一>警告; - )
易。您可以使用自定义的应用程序上下文的单元测试。还是你不使用一个在所有和您手动创建和注入你的豆类。
这听起来我喜欢你的测试可能有点过于宽泛。单元测试应该是测试,油井,单位。一个Spring bean是一个单位的一个很好的例子。你不应该需要的是一个完整的应用程序上下文。我觉得,如果你的单元测试,你需要数百豆,数据库连接,等等,那么你有打算在第二天变化打破真的很脆弱的单元测试这么高的水平,将很难维持,真正ISN”吨加入大量的值。
您可以使用进口功能在测试应用上下文中督促豆加载并覆盖你想要的人。例如,我的督促数据源通常通过JNDI查找获得的,但是当我测试我使用的DriverManager数据源,以便我没有启动应用程序服务器来测试。
我没有信誉分堆在duffymo的答案,但我只是想插话,说他对我来说是“正确”的答案。
在单元测试的设置与自定义的applicationContext.xml实例化一个FileSystemXmlApplicationContext来。在这种自定义XML,在顶部,做一个为duffymo表示。然后宣布你的模拟豆类,非JNDI数据源等,这将覆盖进口申报的ID的。
工作就像我的一个梦想。
您不需要使用任何测试环境(没关系是一种基于XML或Java)。由于春季启动1.4有可用的新注释的 @MockBean
其引入用于嘲笑和Spring豆的天然的Alexa支持。
也许你可以使用预选赛的豆?你会重新定义你想要在一个单独的应用程序上下文来模仿和带有限定词“测试”标签他们的豆子。在单元测试中,接线的豆时始终指定限定符“测试”使用模拟窗口。
我想要做同样的事情,而且我们发现它是必不可少的。
我们使用当前机制是相当手册,但它的工作原理。
例如说,你要模拟出Y型的豆我们做的是每一个有这种依赖使我们实现一个接口,豆 - “IHasY”。此接口是
interface IHasY {
public void setY(Y y);
}
然后在我们的测试中,我们称之为UTIL方法...
public static void insertMock(Y y) {
Map invokers = BeanFactory.getInstance().getFactory("core").getBeansOfType(IHasY.class);
for (Iterator iterator = invokers.values().iterator(); iterator.hasNext();) {
IHasY invoker = (IHasY) iterator.next();
invoker.setY(y);
}
}
我不希望创建一个完整的XML文件只是注入新的依赖,这就是为什么我喜欢这个。
如果你愿意创建一个XML配置文件,然后去是创建一个新的工厂,模拟豆类,使你的出厂默认这个工厂的父母的方式。确保那么您加载在新的子工厂所有的豆类。当这样的分厂将覆盖豆父厂当豆ID是一样的。
现在,如果在我的测试,如果我可以的程序的创建一个工厂,这将是真棒。不必使用XML只是太麻烦了。我希望创建一个子工厂代码。然后,每个测试可以配置它的工厂,它希望的方式。没有理由为什么像这样的工厂将无法正常工作。
弹簧重新注入的设计有嘲笑替代豆。
由于OP这已沿来: Springockito