在unit testing环境中重新定义Spring bean

我们使用Spring来实现我的应用程序,并且使用Spring Testing框架进行unit testing。 我们有一个小问题:应用程序代码从类path中的位置列表(xml文件)加载Spring应用程序上下文。 但是当我们运行我们的unit testing时,我们希望一些Spring bean是嘲讽而不是完整的实现类。 此外,对于某些unit testing,我们希望某些bean成为嘲讽,而对于其他unit testing,我们希望其他bean成为嘲笑,因为我们正在testing应用程序的不同层。

所有这一切意味着我想重新定义应用程序上下文的特定的豆,并在需要时刷新上下文。 在这样做的时候,我想重新定义位于一个(或几个)原始的xml bean定义文件中的一小部分bean。 我找不到一个简单的方法来做到这一点。 人们一直认为Spring是一个unit testing友好的框架,所以我必须在这里丢失一些东西。

你有什么想法如何做到这一点?

谢谢。

我会build议一个自定义的TestClass和Spring 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; } } 

如果指定位置有mock-bean.xml,它们将覆盖“正常”位置上的所有“真实”bean.xml – 您的正常位置可能不同

但是…我永远不会混用模拟和非模拟的bean,当应用程序变得更老时,很难追踪问题。

Spring被描述为testing友好的原因之一是因为在unit testing中可能很容易只是新的或模拟的东西。

或者,我们已经使用了以下设置,取得了巨大的成功,我认为它与您想要的非常接近,我强烈build议:

对于在不同的上下文中需要不同实现的所有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存在于unit testing夹具中。 您可能会遇到需要更多注释,但这是一个很好的开始。

如果你有很多的spring.xml,你可能需要做一些基本上只包含组件扫描定义的新的spring xml文件。 您通常只需将这些文件附加到常规的@ContextConfiguration列表中。 原因是因为你经常会得到上下文扫描的不同configuration(相信我,如果你正在进行networkingtesting,那么你至less做出另外一个注释,这使得4个相关的组合)

那你基本上用的了

 @ContextConfiguration(locations = { "classpath:/path/to/root-config.xml" }) @RunWith(SpringJUnit4ClassRunner.class) 

请注意,此设置不允许您有存根/实时数据的交替组合。 我们尝试了这一点,而且我认为这导致了一个混乱,我不会推荐任何人;)我们要么连线全套存根或全套现场服务。

在testinggui附近的东西时,我们主要使用自动连接的stub依赖关系,依赖关系通常非常重要。 在代码的更清洁的地方,我们使用更常规的unit testing。

在我们的系统中,我们有以下用于组件扫描的xml文件:

  • 定期进行网页制作
  • 仅用于存根启动web
  • 用于集成testing(在junit中)
  • unit testing(在junit中)
  • seleniumtesting(在junit)

这意味着我们完全有5个不同的系统configuration,我们可以启动应用程序。 由于我们只使用注解,因此即使是我们想要连接的unit testing,spring也是足够快的。 我知道这是非传统的,但它真的很棒。

外出整合testing运行全面的现场设置,一两次,我已经决定得到真正的务实,并希望有一个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); } } } 

我知道testing纯粹主义者将会为此而遍布我。 但是,有时候,这种解决scheme非常实用,如果替代品真的很难看,那么这个解决scheme会非常优雅。 再次通常在那些gui-near地区。

使用@InjectedMock注释来查看本教程

这节省了我很多时间。 你只是用

 @Mock SomeClass mockedSomeClass @InjectMock ClassUsingSomeClass service @Before public void setUp() { MockitoAnnotations.initMocks(this); } 

所有的问题都解决了 Mockito将用模拟replaceSpringdependency injection。 我只是用它自己,它很好。

这里列出了一些非常复杂和强大的解决scheme。

但是有一个FAR,FAR更简单的方法来完成Stas所要求的,除了testing方法中的一行代码以外不涉及修改任何东西。 它适用于unit testing和Spring集成testing,适用于自动布线依赖,私有和受保护的域。

这里是:

 junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject); 

你也可以编写你的unit testing,不需要任何查询:

 @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() { ... } } 

这提供了一个简单的方法来混合和匹配真正的configuration文件与testingconfiguration文件。

例如,当使用hibernate时,我可能会在一个configuration文件(在testing和主应用程序中使用)中使用我的sessionFactory bean,并通过dataSource bean在另一个configuration文件中使用(可以使用DriverManagerDataSource来进行in-内存数据库,另一个可能使用JNDI查找)。

但是,一定要注意@ cletus的警告;-)

简单。 您为unit testing使用自定义应用程序上下文。 或者你根本不使用它,而是手动创build并注入你的bean。

这听起来像你的testing可能有点太宽泛。 unit testing是关于testing,以及单元。 Spring bean是一个单元的很好的例子。 你不应该需要一个完整的应用程序上下文。 我发现,如果你的unit testing是如此之高以至于你需要数百个bean,数据库连接等,那么你将有一个非常脆弱的unit testing,这个testing将会在下一次的变化中破裂,难以维护,增加了很多的价值。

您可以在testing应用程序上下文中使用导入function来加载prod bean并覆盖所需的。 例如,我的prod数据源通常是通过JNDI查找获取的,但是当我testing时我使用DriverManager数据源,所以我不必启动应用服务器来testing。

我没有名誉点来堆积duffymo的答案,但我只是想插话说,他是我的“正确的”答案。

在您的unit testing的设置中使用自定义applicationContext.xml实例化FileSystemXmlApplicationContext。 在那个自定义的xml中,在顶部,按照duffymo的指示。 然后声明你的模拟bean,非JNDI数据源等,这将覆盖导入中声明的id。

对我来说就像做梦一样。

您不需要使用任何testing上下文(无关紧要的是基于XML或Java)。 自Spring引导1.4以来,有可用的新注释@MockBean引入了对Spring Beans的@MockBean和Spying的本地支持。

也许你可以为你的豆使用限定符? 您将重新定义要在单独的应用程序上下文中模拟的bean,并使用限定符“test”标记它们。 在你的unit testing中,当连接你的bean时,总是指定限定词“test”来使用模拟ups。

我想做同样的事情,我们发现它是必不可less的。

目前我们使用的机制是相当手动的,但它的工作原理。

比方说,你想模拟出Ytypes的bean。我们所做的每一个有这个依赖关系的bean都实现了一个接口 – “IHasY”。 这个界面是

 interface IHasY { public void setY(Y y); } 

然后在我们的testing中,我们调用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); } } 

我不想创build一个完整的XML文件只是为了注入这个新的依赖,这就是为什么我喜欢这个。

如果你愿意创build一个xmlconfiguration文件,那么要用模拟bean创build一个新的工厂,并使你的默认工厂成为这个工厂的父项。 确保从新的子工厂加载所有的bean。 当这样做的时候,当bean的id相同的时候,子工厂会覆盖父工厂的bean。

现在,如果在我的testing中,如果我可以以编程方式创build一个工厂,那将是非常棒的。 不得不使用XML是太麻烦了。 我正在寻找创build代码的儿童工厂。 然后每个testing都可以按照自己想要的方式configuration工厂。 没有理由为什么这样的工厂将无法正常工作。

spring-reinject的目的是用嘲笑替代豆子。

由于OP已经出现: Springockito