如何为我的 Spring Boot 控制器执行单元测试?

Posted

技术标签:

【中文标题】如何为我的 Spring Boot 控制器执行单元测试?【英文标题】:How to perform unit tests for my spring boot controller? 【发布时间】:2018-06-03 14:24:25 【问题描述】:

我是 TDD 的新手,目前我正在学习一些关于如何为我的 Spring Boot API 编写自动化测试的知识。

所以,我有一个应用程序可以从我的数据库中恢复一堆报告。

我尝试使用我正在阅读的书中的自定义测试。但是测试不起作用。

我总是尝试运行我的测试,但我遇到了一个异常:

java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 1 in XML document from class path resource [application.properties] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:398)
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:335)
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:303)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:187)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:223)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:194)
    at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:258)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadBeanDefinitions(AbstractGenericContextLoader.java:257)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:124)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:107)
    at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:243)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
    ... 25 more
Caused by: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.
    at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:204)
    at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:178)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:400)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLScanner.reportFatalError(XMLScanner.java:1471)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:963)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:602)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112)
    at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:532)
    at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:888)
    at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:824)
    at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
    at java.xml/com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243)
    at java.xml/com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339)
    at org.springframework.beans.factory.xml.DefaultDocumentLoader.loadDocument(DefaultDocumentLoader.java:77)
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadDocument(XmlBeanDefinitionReader.java:428)
    at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
    ... 38 more

这是我正在尝试运行的测试。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:application.properties")
@ActiveProfiles("local")
public class ReportControllerTest 

    @Autowired
    private MockMvc mvc;

    @Test
    public void doesRecoverPercentageOfInfectedPatients() throws Exception 

        MvcResult result = mvc.perform(get("/api/report/infected").contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk()).andReturn();


        assertThat(result.getResponse().getStatus()).equals(status().isOk());
    


关于如何正确运行此测试或我做错了什么的任何建议或建议?

更新:

主类

@SpringBootApplication
public class ReportApiApplication 

    public static void main(String[] args) 
        SpringApplication.run(ReportApiApplication.class, args);
    

我的 application.properties 如下所示:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zssn?useSSL=false
spring.datasource.username=root
spring.datasource.password=root

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

【问题讨论】:

您能否提供有关您的项目设置的更多信息?是xml还是java配置?你见过这个问题吗? ***.com/questions/24776669/… @Karapapas 在我的情况下,我只是使用 application.properties 文件,我将所有数据用于访问我的数据库和配置到休眠框架。 你能粘贴你的属性文件吗?我认为该错误为您提供了有关该问题的很好的提示。 Line 1 in XML document from class path resource [application.properties] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog. @Karapapas 我在问题的末尾添加了我的 application.properties。有什么问题吗? 属性文件看起来不错。您的项目中是否有任何 .xml 文件?也许您的 xml 文件的格式有问题。你用什么IDE? 【参考方案1】:

您应该如下更改 ReportControllerTest。需要删除@ContextConfiguration(locations="classpath:application.properties")

同时在你的@ActiveProfiles("local") 的资源目录下创建application.local.properties

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
@ActiveProfiles("local")
public class ReportControllerTest 

    @Autowired
    private MockMvc mvc;

    @Test
    public void doesRecoverPercentageOfInfectedPatients() throws Exception 

        MvcResult result = mvc.perform(get("/api/report/infected").contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk()).andReturn();


        assertThat(result.getResponse().getStatus()).equals(status().isOk());
    


【讨论】:

嗨@gaurav-srivastav 我尝试了你的建议,但我仍然遇到同样的问题。 您使用的是哪个版本的 Spring Boot? 我使用的是2.0.2版本。 Junit 版本?如果您使用 Junit4 或如果使用 Junit 5 则使用 SpringRunner 则使用 SpringExtension 您是否使用 XML 定义来定义 bean 或 application.properties?【参考方案2】:

经过一些研究和有益的谈话后,我发现了问题所在。 所以,实际上我使用的是 Java 10、JUnit 4 和 mockito 2。 我的 API 工作正常。但是当我尝试构建工件时,我注意到一些 hibernate 类没有正确编译,这是因为 hibernate 还没有完全支持 Java 10。

我用这个作为我的论据,发现 JUnit 和 Mockito 也没有完全支持 java 10。 因此,我所做的只是将我的开发环境更改为 Java 8,这样我的测试就可以正常工作了。

这是我的一个测试的最终版本。我根据我在 cmets 上获得的所有建议做了一些更改。

@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
public class ReportControllerTest 

    @Autowired
    private MockMvc mvc;

    @Autowired
    private WebApplicationContext ctx;

    @Before
    public void setUp() 
        this.mvc = MockMvcBuilders.webAppContextSetup(this.ctx).build();
    

    @Test
    public void doesRecoverPercentageOfInfectedPatients() throws Exception 
        mvc.perform(MockMvcRequestBuilders.get("/api/report/infected")
                .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk());
    


【讨论】:

以上是关于如何为我的 Spring Boot 控制器执行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

如何为具有 Spring Security 配置的 Spring Boot API 编写单元测试

如何为 ZuulException 自定义 Spring Boot 控制器 API 响应

如何为JSP配置spring boot mvc app?

如何为后端控制器执行单元测试

如何为 Spring Boot 2 添加自定义 MeterRegisty

使用 Spring Security 进行 Spring Boot 单元测试