Spring bean在单元测试环境中的重定义

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/565334/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 03:49:22  来源:igfitidea点击:

Spring beans redefinition in unit test environment

springunit-testingspring-test

提问by Stas

We are using Spring for my application purposes, and Spring Testing framework for unit tests. We have a small problem though: the application code loads a Spring application context from a list of locations (XML files) in the classpath. But when we run our unit tests, we want some of the Spring beans to be mocks instead of full-fledged implementation classes. Moreover, for some unit tests we want some beans to become mocks, while for other unit tests we want other beans to become mocks, as we are testing different layers of the application.

我们将 Spring 用于我的应用程序,并使用 Spring 测试框架进行单元测试。但是我们有一个小问题:应用程序代码从类路径中的位置列表(XML 文件)加载 Spring 应用程序上下文。但是当我们运行我们的单元测试时,我们希望一些 Spring bean 是模拟而不是成熟的实现类。此外,对于某些单元测试,我们希望某些 bean 成为模拟,而对于其他单元测试,我们希望其他 bean 成为模拟,因为我们正在测试应用程序的不同层。

All this means I want to redefine specific beans of the application context and refresh the context when desired. While doing this, I want to redefine only a small portion of the beans located in one (or several) original XML beans definition file. I cannot find an easy way to do it. It's always regarded that Spring is a unit-testing-friendly framework, so I must be missing something here.

所有这一切意味着我想重新定义应用程序上下文的特定 bean 并在需要时刷新上下文。在执行此操作时,我只想重新定义位于一个(或多个)原始 XML bean 定义文件中的一小部分 bean。我找不到一种简单的方法来做到这一点。人们一直认为 Spring 是一个对单元测试友好的框架,所以我一定在这里遗漏了一些东西。

Do you have any ideas how to do it?

你有什么想法怎么做吗?

Thanks!

谢谢!

采纳答案by Michael Pralow

I would propose a custom TestClassand some easy rules for the locations of the spring bean.xml:

我会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;
    }
}

If there are mock-bean.xmlin the specified location, they will override all "real" bean.xmlfiles in the "normal" locations - your normal locations might differ.

如果mock-bean.xml在指定位置存在,它们将覆盖bean.xml“正常”位置中的所有“真实”文件 - 您的正常位置可能会有所不同。

But … I would never mix mock and non-mock beans, as it's hard to trace problems when the application grows older.

但是……我永远不会混合模拟和非模拟 bean,因为当应用程序变老时很难跟踪问题。

回答by krosenvold

One of the reasons spring is described as test-friendly is because it may be easy to just newor mock stuff in the unit test.

spring 被描述为测试友好的原因之一是因为它可能很容易在单元测试中添加新的或模拟的东西。

Alternately we have used the following setup with great success, and I think it is quite close to what you want, I would stronglyrecommend it:

或者,我们已经成功地使用了以下设置,我认为它非常接近您想要的,我强烈推荐它:

For all beans that need different implementations in different contexts, switch to annotation based wiring. You can leave the others as-is.

对于在不同上下文中需要不同实现的所有 bean,切换到基于注解的接线。你可以让其他人保持原样。

Implement the following set of annotations

实现以下一组注解

 <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>

Then you annotate your liveimplementations with @Repository, your stubimplementations with @StubRepository, any code that should be present in the unit-test fixture ONLY with @TestScopedComponent. You may run into needing a couple more annotations, but these are a great start.

然后你用@Repository注释你的实时实现,你的存根实现用@StubRepository,任何应该出现在单元测试夹具中的代码只用@TestScopedComponent。您可能会遇到需要更多注释的情况,但这是一个很好的开始。

If you have a lot of spring.xml, you will probably need to make a few new spring xml files that basically only contain the component-scan definitions. You'd normally just append these files to your regular @ContextConfiguration list. The reason for this is because you frequently end up with different configurations of the context-scans (trust me, you willmake at least 1 more annotations if you're doing web-tests, which makes for 4 relevant combinations)

如果您有很多 spring.xml,您可能需要创建一些新的 spring xml 文件,这些文件基本上只包含组件扫描定义。您通常只需将这些文件附加到您的常规 @ContextConfiguration 列表中。这样做的原因是因为你经常与上下文扫描的不同配置(相信我,你最终做出至少1点以上的注解,如果你在做网络测试,这使得4名相关的组合)

Then you basically use the

然后你基本上使用

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

Note that this setup does notallow you to have alternating combinations of stub/live data. We tried this, and I think that resulted in a mess I wouldn't recommend anyone ;) We either wire inn the full set of stubs or the full set of live services.

请注意,此安装程序不会让你有存根/实时数据的交替组合。我们尝试了这个,我认为这导致了混乱,我不会向任何人推荐 ;) 我们要么为旅馆提供全套存根,要么提供全套实时服务。

We mainly use auto-wired stub dependencies when testing gui near stuff where the dependencies are usually quite substantial. In cleaner areas of the code we use more regular unit-testing.

我们主要使用自动连接的存根依赖项来测试 gui 附近的东西,而这些东西依赖项通常非常重要。在代码的清洁区域,我们使用更常规的单元测试。

In our system we have the following xml-files for component-scan:

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

  • for regular web production
  • for starting web with stubs only
  • for integration tests (in junit)
  • for unit tests (in junit)
  • for selenium web tests (in junit)
  • 用于常规网页制作
  • 仅使用存根启动网络
  • 用于集成测试(在 junit 中)
  • 用于单元测试(在 junit 中)
  • 用于硒网络测试(在 junit 中)

This means we totally have 5 different system-wide configurations that we can start the application with. Since we only use annotations, spring is fast enough to autowire even those unit tests we want wired. I know this is untraditional, but it's really great.

这意味着我们总共可以使用 5 种不同的系统范围配置来启动应用程序。由于我们只使用注解,spring 足够快,甚至可以自动连接那些我们想要连接的单元测试。我知道这是非传统的,但它真的很棒。

Out integration tests run with full live setup, and once or twice I have decided to get reallypragmatic and want to have a 5 live wirings and a single mock:

集成测试在完整的实时设置下运行,有一两次我决定变得非常务实,并希望有 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);
     }


   }
}

I know the test purists are going to be all over me for this. But sometimes it's just a very pragmatic solution that turns out to be very elegant when the alternative would be really really ugly. Again it's usually in those gui-near areas.

我知道测试纯粹主义者会为此而关注我。但有时它只是一个非常务实的解决方案,当替代方案非常丑陋时,结果证明它非常优雅。同样,它通常在那些 gui-near 区域。

回答by Ev0oD

See this tutorial with @InjectedMock annotation

使用@InjectedMock 注释查看本 教程

It saved me a lot of time. You just use

它为我节省了很多时间。你只要用

@Mock
SomeClass mockedSomeClass

@InjectMock
ClassUsingSomeClass service

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}

and all your problems are solved. Mockito will replace the spring dependency injection with a mock. I just used it myself and it works great.

你所有的问题都解决了。Mockito 将用模拟替换 spring 依赖注入。我自己刚用过,效果很好。

回答by Daniel Alexiuc

There are some very complicated and powerful solutions listed here.

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

But there is a FAR, FAR simplerway to accomplish what Stas has asked, which doesn't involve modifying anything other than one line of code in the test method. It works for unit tests and Spring integration tests alike, for autowired dependencies, private and protected fields.

但是有一种FAR,FAR 更简单的方法来完成 Stas 的要求,它不涉及修改测试方法中的一行代码以外的任何内容。它适用于单元测试和 Spring 集成测试,适用于自动装配的依赖项、私有和受保护的字段。

Here it is:

这里是:

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

回答by cletus

Easy. You use a custom application context for your unit tests, or you don't use one at all and you manually create and inject your beans.

简单。您可以为单元测试使用自定义应用程序上下文,或者根本不使用,而是手动创建和注入 bean。

It sounds to me like your testing might be a bit too broad. Unit testing is about testing, well, units. A Spring bean is a pretty good example of a unit. You shouldn't need an entire application context for that. I find that if your unit testing is so high-level that you need hundreds of beans, database connections etc., you have a really fragile unit test that is going to break on the very next change, will be hard to maintain and really isn't adding a lot of value.

在我看来,您的测试可能有点过于广泛。单元测试是关于测试,嗯,单元。Spring bean 是一个很好的单元示例。您不应该为此需要整个应用程序上下文。我发现,如果您的单元测试如此高级,以至于您需要数百个 bean、数据库连接等,那么您的单元测试非常脆弱,它将在下一次更改时中断,将难以维护,而且确实如此不会增加很多价值。

回答by toolkit

You can also write your unit tests to not require any lookups at all:

您还可以编写单元测试,根本不需要任何查找:

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

This gives an easy way to mix and match real config files with test config files.

这提供了一种将真实配置文件与测试配置文件混合和匹配的简单方法。

For example, when using hibernate, I might have my sessionFactory bean in one config file (to be used in both the tests and the main app), and have by dataSource bean in another config file (one might use a DriverManagerDataSource to an in-memory db, the other might use a JNDI-lookup).

例如,在使用 hibernate 时,我可能将 sessionFactory bean 放在一个配置文件中(在测试和主应用程序中都使用),并在另一个配置文件中使用 dataSource bean(可能使用 DriverManagerDataSource 到 in-内存数据库,另一个可能使用 JNDI 查找)。

But, definitely take heed of @cletus'swarning ;-)

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

回答by duffymo

You can use the importfeature in your test app context to load in the prod beans and override the ones you want. For example, my prod data source is usually acquired via JNDI lookup, but when I test I use a DriverManager data source so I don't have to start the app server to test.

您可以在测试应用程序上下文中使用导入功能来加载 prod bean 并覆盖您想要的那些。例如,我的 prod 数据源通常是通过 JNDI 查找获取的,但是当我测试时,我使用的是 DriverManager 数据源,因此我不必启动应用程序服务器进行测试。

回答by duffymo

I don't have the reputation points to pile on duffymo's answer, but I just wanted to chime in and say his was the "right" answer for me.

我没有足够的声望来评价 duffymo 的答案,但我只是想插嘴说他对我来说是“正确”的答案。

Instantiate a FileSystemXmlApplicationContext in your unit test's setup with a custom applicationContext.xml. In that custom xml, at the top, do an as duffymo indicates. Then declare your mock beans, non-JNDI data sources, etc, that will override the id's declared in the import.

使用自定义 applicationContext.xml 在单元测试设置中实例化 FileSystemXmlApplicationContext。在该自定义 xml 中,在顶部执行 duffymo 指示的操作。然后声明您的模拟 bean、非 JNDI 数据源等,它们将覆盖导入中声明的 id。

Worked like a dream for me.

对我来说就像做梦一样工作。

回答by luboskrnac

You do not need to use any test contexts (doesn't matter is XML or Java based). Since Spring boot 1.4 there is available new annotation @MockBeanwhich introduced native support for mocking and Spying of Spring Beans.

您不需要使用任何测试上下文(无论是基于 XML 还是基于 Java)。从 Spring boot 1.4 开始,有可用的新注解@MockBean,它引入了对 Spring Beans 的模拟和监视的本机支持。

回答by Sergey Grigoriev

spring-reinjectis designed to substitute beans with mocks.

spring-reinject旨在用模拟替换 bean。