跨JUnit测试类重用Spring应用程序上下文

97

我们有一堆JUnit测试用例(集成测试),它们被逻辑地分组到不同的测试类中。

根据http://static.springsource.org/spring/docs/current/spring-framework-reference/html/testing.html所述,我们能够每个测试类加载一次Spring应用程序上下文,并将其重复使用于JUnit测试类中的所有测试用例中。

但是我们想知道是否有一种方法可以仅为一堆JUnit测试类加载一次Spring应用程序上下文。

值得一提的是,我们使用Spring 3.0.5,JUnit 4.5,并使用Maven构建项目。


10
以下所有答案都很好,但是我没有一个context.xml文件。我是否已经标注得无法回头了?有没有不使用context.xml的方法来做到这一点? - markthegrea
4
你找到解决方案的答案了吗?我有同样的问题,想用注释和Spring Boot 来完成这个。 - alext
5个回答

110

可以的,这完全是可能的。你所要做的就是在测试类中使用相同的locations属性:

@ContextConfiguration(locations = "classpath:test-context.xml")

Spring通过locations参数缓存应用程序上下文,因此如果第二次出现相同的locations,Spring将使用相同的上下文而不是创建新的上下文。

我写了一篇关于这个特性的文章:Speeding up Spring integration tests。Spring文档中也详细描述了这个特性:9.3.2.1 上下文管理和缓存

这有一个有趣的含义。因为Spring不知道JUnit何时完成,它将所有上下文缓存永久并使用JVM关闭钩子关闭它们。这种行为(尤其是当您有许多测试类具有不同的locations时)可能会导致过多的内存使用、内存泄漏等问题。这是缓存上下文的另一个优势。


1
我更愿意说,Spring 对于测试用例的执行顺序没有任何了解。因此,它无法判断上下文是否稍后需要使用或可以被处理。 - philnate
1
我不认为这是真的。每次我执行Run As/JUnit测试时,Eclipse/JUnit都要花费2分钟来启动环境。如果有任何缓存,这种情况就不会发生。 - user1944491
4
有没有想法可以完全通过注释来定义上下文,而不使用XML呢?我在文档和StackOverflow上搜索了很多,但找不到任何线索,这让我认为这似乎不可能。 - Jean-François Savard
1
@Jean-FrançoisSavard - 在@ContextConfiguration注解属性中使用classes而不是locations - Przemek Bielicki
1
可以通过@SpringBootTest来实现/组合吗? - alext
显示剩余8条评论

30

除了Tomasz Nurkiewicz的答案之外,从Spring 3.2.2开始,可以使用@ContextHierarchy注解来拥有单独的、关联的多个上下文结构。当多个测试类想要共享(例如)内存数据库设置(数据源、EntityManagerFactory、tx管理器等)时,这非常有用。

例如:

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("FirstTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class FirstTest {
 ...
}

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("SecondTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class SecondTest {
 ...
}
有了这个设置,使用"test-db-setup-context.xml"的上下文将仅被创建一次,但其中的bean可以注入到各个单元测试的上下文中。
了解更多信息,请参见手册:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#testcontext-ctx-management(搜索“context hierarchy”)。

我有一个多模块的Maven项目,我试图避免在服务模块中设置数据库(因为数据访问模块已经加载了测试数据),但这对我来说行不通! - Muhammad Hewedy
5
这对我很有帮助!谢谢。只想明确一下,如果没有@ContextHierarchy注释,Spring会为每个测试加载我的数据库。我正在使用“classes”参数:@ContextConfiguration(classes = {JpaConfigTest.class, ... - Brel
5
有没有想过是否可以完全通过注释来定义上下文,而不是使用XML?我在文档和SO上搜索了很多,但没有找到任何线索让我觉得这不可能。请问这可行吗? - Jean-François Savard
1
@Jean-FrançoisSavard,你在搜索中有什么进展了吗(使用注释而不是XML)? - javadev
@javadev,希望这是你要找的内容 https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#testcontext-ctx-management-caching - Raviteja Gubba
除了将上下文拆分为“共享”和“独立”之外,这是否与预期的答案完全相同? - b15

7

一个显著的问题是,如果我们在使用@SpringBootTests时又在不同的测试类中使用@MockBean,则Spring无法为所有测试重用其应用程序上下文。

解决方法是将所有@MockBean移动到一个通用的抽象类中,这样可以解决该问题。

@SpringBootTests(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
public abstract class AbstractIT {

   @MockBean
   private ProductService productService;

   @MockBean
   private InvoiceService invoiceService;

}

那么测试类可以如下所示

public class ProductControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchProduct_ShouldSuccess() {
   }

}

public class InvoiceControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchInvoice_ShouldSuccess() {
   }

}

6

基本上,如果您在不同的测试类中具有相同的应用程序上下文配置,则Spring足够智能,可以为您配置此项。例如,假设您有两个类A和B如下:

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

在这个例子中,类A模拟bean C,而类B模拟bean D。因此,Spring将这些视为两个不同的配置,因此对于类A和类B分别加载应用程序上下文一次。
如果我们希望Spring在这两个类之间共享应用程序上下文,它们应该如下所示:
@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

如果您按照这种方式将类连接起来,Spring将仅针对A或B类之一加载应用程序上下文,具体取决于哪个类在测试套件中先运行。 这可以在多个测试类之间复制,唯一的条件是不应以不同的方式定制测试类。 任何导致测试类与其他类(在Spring眼中)不同的自定义都将导致Spring创建另一个应用程序上下文。

0

创建您的配置类,如下所示:

@ActiveProfiles("local")
@RunWith(SpringJUnit4ClassRunner.class )
@SpringBootTest(classes ={add your spring beans configuration classess})
@TestPropertySource(properties = {"spring.config.location=classpath:application"})
@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
public class RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(S2BXISINServiceTest.class);


    //auto wire all the beans you wanted to use in your test classes
    @Autowired
    public XYZ xyz;
    @Autowired
    public ABC abc;


    }



Create your test suite like below



@RunWith(Suite.class)
@Suite.SuiteClasses({Test1.class,test2.class})
public class TestSuite extends RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(TestSuite.class);


}

按照以下方式创建您的测试类

public class Test1 extends RunConfigration {


  @Test
    public void test1()
    {
    you can use autowired beans of RunConfigration classes here 
    }

}


public class Test2a extends RunConfigration {

     @Test
    public void test2()
    {
    you can use autowired beans of RunConfigration classes here 
    }


}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接