集成测试
集成测试(Integration Testing,缩写为INT)将多个模块结合在一起进行测试,确保多个组件可以正确交互。当它失败表示你的各个代码块间无法有效协作。
集成测试可以是下面其中一项:
- 测试两个或多个类之间的交互。
- 包含多层的测试。包括业务服务和持久层之间的交互。
- 包含整个应用程序整个路径的测试。在这些测试中,我们向应用程序发送一个请求,并检查它是否正确响应并根据我们的期望更改了数据库状态。
@SpringBootTest的集成测试
Spring Boot提供了@SpringBootTest注释,可用于创建一个的application context上下文,这个application context中的对象(比如server层,dao层的对象)都是被Spring真实管理的,并不是通过mock方式虚拟出来的。但是请注意,过度使用@SpringBootTest 可能会导致测试的运行时间很长。
事实上,我们应该用更多的单元测试去覆盖我们的代码,因为在自动化测试的金字塔中,单元测试的占比应该是最大的,在单元测试中,我们可以手动创建需要测试的对象,然后通过Stub和Mock来模拟这些外部依赖的对象。比如测试Controller层的时候,我们可以把service层Mock掉,测试service层的时候我们可以把dao层Mock掉。尽可能多的单元测试来代替集成测试,因为使用集成测试需要Spring在每次启动测试时都启动整个应用程序上下文,会导致自动化测试的运行时间过长。而且一旦应用程序变大,Spring需要花更多的时间,因为Spring必须将越来越多的bean加载到应用程序上下文中。
我个人认为应该把每个测试的方法拆分的更加细,不是简单从请求到DB的流程,而是应该专注你需要测试的业务逻辑,可以把依赖的接口或者不是你关注重点的依赖数据都可以Mock和Stub掉。用更多的单元测试去覆盖,这样一方面可以减少自动化测试的运行时间,因为不需要启动整个应用程序上下文,而且更加能保证代码的质量。
当然,对于需要覆盖从传入请求到数据库的整个Spring Boot应用程序的测试,或者需要测试到与数据库交互或者业务服务和持久层之间交互的,我们可以并且应该使用@SpringBootTest。
Spring Boot Test集成测试环境搭建
-
首先我这里提取了一个专门测试用的module,用来放跟测试有关的common类,还有管理测试相关的依赖包
-
我们加入测试需要的依赖。
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
</dependency>
<dependency>
<groupId>com.cc.cloud</groupId>
<artifactId>cloud-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
并在parent pom文件中管理依赖的版本。
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.7</version>
</dependency>
下面介绍一下上面的一些依赖的作用。
- json-path 是json解析工具,当我们测试controller层的时候,可以很方便的解析并测试我们返回的json数据。
- javafaker是测试造数据的神器,可以帮我们生成姓名,地址之类的测试数据。
- cloud-common 这个是我抽取的一些common需要用到的类或者依赖。
- spring-tx和jackson-databind和spring-boot-starter-web是我们一会抽取测试基类的时候会用到的依赖。
- 抽取测试基类
package com.cc.cloud.test.common;
import com.github.javafaker.Faker;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringRunner.class)
@ActiveProfiles({"test"})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)//目录层次结构与主类一致,classes属性可以不指定启动类
@FixMethodOrder(MethodSorters.NAME_ASCENDING) //JUnit中按照字母顺序执行测试
@Transactional //@Transactional 注释标签是表明此测试类的事务启用,这样所有的测试方案都会自动的rollback,即不用自己清除自己所做的任何对数据库的变更了
@Ignore
public abstract class IntegrationTestBase {
private Faker faker;
public Faker getFaker() {
return this.faker;
}
@Before
public void initIntegrationTestBase() {
this.faker = new Faker();
}
}
这里抽取了一个测试的基类IntegrationTestBase,用于所有的集成测试的基类。
然后下面有抽取了一个测试的基类,继承了IntegrationTestBase。用于所有Controller层测试的基类。
package com.cc.cloud.test.common;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Ignore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@Ignore
public abstract class ControllerTestBase extends IntegrationTestBase {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
private ObjectMapper jsonMapper;
@Before
public void initControllerTestBase() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
this.jsonMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
public MockMvc getMockMvc() {
return this.mockMvc;
}
public ObjectMapper getJsonMapper() {
return this.jsonMapper;
}
}
- Spring Boot Test集成测试的配置。
我们创建一个bootstrap-test.yml文件,首先为什么是这个命名呢,因为我们前面指定了@ActiveProfiles({"test"})
,所以我们profiles指定了test,所以后缀需要加上test。但是为什么不是application-test.yml文件呢?因为我试过在application-test.yml配置,但是发现测试运行是报错的。
21:24:06.698 [main] DEBUG org.springframework.test.context.junit4.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.cc.cloud.member.controller.MemberControllerTest]
21:24:06.719 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
21:24:06.760 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
21:24:06.858 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.cc.cloud.member.controller.MemberControllerTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
21:24:06.907 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.cc.cloud.member.controller.MemberControllerTest], using SpringBootContextLoader
21:24:06.916 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.cc.cloud.member.controller.MemberControllerTest]: class path resource [com/cc/cloud/member/controller/MemberControllerTest-context.xml] does not exist
21:24:06.919 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.cc.cloud.member.controller.MemberControllerTest]: class path resource [com/cc/cloud/member/controller/MemberControllerTestContext.groovy] does not exist
21:24:06.919 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.cc.cloud.member.controller.MemberControllerTest]: no resource found for suffixes {-context.xml, Context.groovy}.
21:24:06.922 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.cc.cloud.member.controller.MemberControllerTest]: MemberControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
21:24:07.471 [main] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [C:\Users\c\Desktop\spring-cloud-demo\cloud-service-member\target\classes\com\cc\cloud\member\MemberApp.class]
21:24:07.473 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.cc.cloud.member.MemberApp for test class com.cc.cloud.member.controller.MemberControllerTest
21:24:08.010 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.cc.cloud.member.controller.MemberControllerTest]: using defaults.
21:24:08.011 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
21:24:08.116 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@57d7f8ca, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@76c3e77a, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@78123e82, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@67c33749, org.springframework.test.context.support.DirtiesContextTestExecutionListener@fba92d3, org.springframework.test.context.transaction.TransactionalTestExecutionListener@662b4c69, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@fa49800, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@71238fc2, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@2a54a73f, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@16a0ee18, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@3d6f0054, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@505fc5a4]
21:24:08.121 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.cc.cloud.member.controller.MemberControllerTest]
21:24:08.122 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.cc.cloud.member.controller.MemberControllerTest]21:24:08.142 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.cc.cloud.member.controller.MemberControllerTest]
21:24:08.142 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.cc.cloud.member.controller.MemberControllerTest]21:24:08.151 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.cc.cloud.member.controller.MemberControllerTest]
21:24:08.151 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.cc.cloud.member.controller.MemberControllerTest]
21:24:08.153 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.cc.cloud.member.controller.MemberControllerTest]
21:24:08.153 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.cc.cloud.member.controller.MemberControllerTest]
21:24:08.214 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@21e360a testClass = MemberControllerTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@5ba3f27a testClass = MemberControllerTest, locations = '{}', classes = '{class com.cc.cloud.member.MemberApp}', contextInitializerClasses = '[]', activeProfiles = '{test}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=0}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@226a82c4, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6b53e23f, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@15bb6bea, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@45f45fa1], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false]], class annotated with @DirtiesContext [false] with mode [null].
21:24:08.215 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.cc.cloud.member.controller.MemberControllerTest]
21:24:08.215 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.cc.cloud.member.controller.MemberControllerTest]
21:24:08.276 [main] DEBUG org.springframework.test.context.support.DependencyInjectionTestExecutionListener - Performing dependency injection for test context [[DefaultTestContext@21e360a testClass = MemberControllerTest, testInstance = com.cc.cloud.member.controller.MemberControllerTest@434a63ab, testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@5ba3f27a testClass = MemberControllerTest, locations = '{}', classes = '{class com.cc.cloud.member.MemberApp}', contextInitializerClasses = '[]', activeProfiles = '{test}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=0}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@226a82c4, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6b53e23f, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@15bb6bea, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@45f45fa1], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false]]].
21:24:08.335 [main] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=0}
2019-11-07 21:24:14.632 INFO [cloud-service-member,,,] 1552 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.retry.annotation.RetryConfiguration' of type [org.springframework.retry.annotation.RetryConfiguration$$EnhancerBySpringCGLIB$$e4319f5] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-11-07 21:24:14.666 INFO [cloud-service-member,,,] 1552 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$349984cd] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-11-07 21:24:19.171 INFO [cloud-service-member,,,] 1552 --- [ main] o.s.cloud.commons.util.InetUtils : Cannot determine local hostname
2019-11-07 21:24:19.195 INFO [cloud-service-member,,,] 1552 --- [ main] o.s.c.n.eureka.InstanceInfoFactory : Setting initial instance status as: STARTING
2019-11-07 21:24:19.297 INFO [cloud-service-member,,,] 1552 --- [ main] com.netflix.discovery.DiscoveryClient : Initializing Eureka in region us-east-1
2019-11-07 21:24:21.815 INFO [cloud-service-member,,,] 1552 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON encoding codec LegacyJacksonJson
2019-11-07 21:24:21.816 INFO [cloud-service-member,,,] 1552 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON decoding codec LegacyJacksonJson
2019-11-07 21:24:22.035 INFO [cloud-service-member,,,] 1552 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML encoding codec XStreamXml
2019-11-07 21:24:22.035 INFO [cloud-service-member,,,] 1552 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML decoding codec XStreamXml
2019-11-07 21:24:22.562 INFO [cloud-service-member,,,] 1552 --- [ main] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2019-11-07 21:24:24.749 INFO [cloud-service-member,,,] 1552 --- [ main] com.netflix.discovery.DiscoveryClient : Disable delta property : false
2019-11-07 21:24:24.749 INFO [cloud-service-member,,,] 1552 --- [ main] com.netflix.discovery.DiscoveryClient : Single vip registry refresh property : null
2019-11-07 21:24:24.749 INFO [cloud-service-member,,,] 1552 --- [ main] com.netflix.discovery.DiscoveryClient : Force full registry fetch : false
2019-11-07 21:24:24.749 INFO [cloud-service-member,,,] 1552 --- [ main] com.netflix.discovery.DiscoveryClient : Application is null : false
2019-11-07 21:24:24.750 INFO [cloud-service-member,,,] 1552 --- [ main] com.netflix.discovery.DiscoveryClient : Registered Applications size is zero : true
2019-11-07 21:24:24.750 INFO [cloud-service-member,,,] 1552 --- [ main] com.netflix.discovery.DiscoveryClient : Application version is -1: true
2019-11-07 21:24:24.750 INFO [cloud-service-member,,,] 1552 --- [ main] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server
2019-11-07 21:24:28.885 ERROR [cloud-service-member,,,] 1552 --- [ main] c.n.d.s.t.d.RedirectingEurekaHttpClient : Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8888/eureka/}
com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect
at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:187) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.filter.GZIPContentEncodingFilter.handle(GZIPContentEncodingFilter.java:123) ~[jersey-client-1.19.1.jar:1.19.1]
at com.netflix.discovery.EurekaIdentityHeaderFilter.handle(EurekaIdentityHeaderFilter.java:27) ~[eureka-client-1.9.8.jar:1.9.8]
at com.sun.jersey.api.client.Client.handle(Client.java:652) ~[jersey-client-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682) ~[jersey-client-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74) ~[jersey-client-1.19.1.jar:1.19.1]
at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:509) ~[jersey-client-1.19.1.jar:1.19.1]
at com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.getApplicationsInternal(AbstractJerseyEurekaHttpClient.java:194) ~[eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.getApplications(AbstractJerseyEurekaHttpClient.java:165) ~[eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.MetricsCollectingEurekaHttpClient.execute(MetricsCollectingEurekaHttpClient.java:73) ~[eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.executeOnNewServer(RedirectingEurekaHttpClient.java:118) ~[eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.execute(RedirectingEurekaHttpClient.java:79) ~[eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:120) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.DiscoveryClient.getAndStoreFullRegistry(DiscoveryClient.java:1051) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.DiscoveryClient.fetchRegistry(DiscoveryClient.java:965) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.DiscoveryClient.<init>(DiscoveryClient.java:414) [eureka-client-1.9.8.jar:1.9.8]
at com.netflix.discovery.DiscoveryClient.<init>(DiscoveryClient.java:269) [eureka-client-1.9.8.jar:1.9.8]
at org.springframework.cloud.netflix.eureka.CloudEurekaClient.<init>(CloudEurekaClient.java:63) [spring-cloud-netflix-eureka-client-2.1.0.RC2.jar:2.1.0.RC2]
at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$EurekaClientConfiguration.eurekaClient(EurekaClientAutoConfiguration.java:249) [spring-cloud-netflix-eureka-client-2.1.0.RC2.jar:2.1.0.RC2]
at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$EurekaClientConfiguration$$EnhancerBySpringCGLIB$$983fd3cd.CGLIB$eurekaClient$2(<generated>) [spring-cloud-netflix-eureka-client-2.1.0.RC2.jar:2.1.0.RC2]
at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$EurekaClientConfiguration$$EnhancerBySpringCGLIB$$983fd3cd$$FastClassBySpringCGLIB$$593f1583.invoke(<generated>) [spring-cloud-netflix-eureka-client-2.1.0.RC2.jar:2.1.0.RC2]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) [spring-core-5.1.3.RELEASE.jar:5.1.3.RELEASE]
这是为什么呢?因为我们测试的这个微服务中配置了spring config client,并且我们已经把我们的配置抽取到了远程的配置中心了。所以当启动测试服务的时候,会先读取bootstrap.yml,然后连接eureka服务,找到config server,然后config client请求config server去读取远程的配置。 但是由于我们的eureka和config server在测试环境下都是没有启动的,自然就会出现连接错误的问题。
这种时候我们需要怎么解决呢?我的解决方法如下:
因为当我们设置了profiles为test的时候,其实会先读取bootstrap.yml然后再读取bootstrap-test.yml,而且后面的配置会覆盖前面的配置。最后结合这两个配置去启动服务。所以我们可以在bootstrap-test.yml去设置关闭我们的eureka还有config client。
下面给我的配置:
spring:
datasource:
druid:
# jdbc:h2:mem:指定databaseName; 内存模式
# DB_CLOSE_DELAY=-1 关闭连接后数据库将被清空,适合测试环境
# MODE=MYSQL 兼容模式为MYSQL
# DB_CLOSE_ON_EXIT=FALSE VM存在时不关闭数据库
url: jdbc:h2:mem:member_service_db;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: sa
password: sa
driver-class-name: org.h2.Driver
platform: h2
# schema: classpath:schema.sql //程序运行时,使用schema.sql来创建数据库中的表
data: classpath:/db/init-data.sql # 程序运行时,使用data.sql来创建初始数据
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
database: h2
show-sql: true
hibernate:
ddl-auto: create-drop # 每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
properties:
hibernate:
show_sql: true # 操作数据库时显示sql语句
use_sql_comments: true # SQL 语句中输出便于调试的注释信息
format_sql: true
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
jdbc:
time_zone: UTC
h2: # 开启h2的控制台
console:
enabled: true
path: /console
settings:
trace: false
web-allow-others: true
cloud:
bus:
enabled: false # 关闭Spring Cloud Bus,关闭连接RabbitMQ
config:
enabled: false # disable config
discovery:
enabled: false # disable config
discovery: # disable eureka
enabled: false
eureka:
client:
enabled: false # disable eureka
- Controller测试
package com.cc.cloud.member.controller;
import com.cc.cloud.member.domain.Member;
import com.cc.cloud.test.common.ControllerTestBase;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
public class MemberControllerTest extends ControllerTestBase {
@Test
public void findAllMembers() throws Exception {
this.getMockMvc()
.perform(
get("/member")
.contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
.andDo(MockMvcResultHandlers.print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.code").value(1))
.andExpect(jsonPath("$.message").value("成功"));
}
@Test
public void addMember() throws Exception {
String name = getFaker().name().fullName();
Member member = new Member();
member.setMemberName(name);
this.getMockMvc()
.perform(
post("/member")
.content(getJsonMapper().writeValueAsString(member))
.contentType(MediaType.APPLICATION_JSON)).andExpect(status().isCreated())
.andDo(MockMvcResultHandlers.print())//输出整个响应结果信息
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.code").value(1))
.andExpect(jsonPath("$.message").value("成功"))
.andExpect(jsonPath("$.result.memberName").value(name));
}
}
- service层测试
package com.cc.cloud.member.service.impl;
import com.cc.cloud.member.domain.Member;
import com.cc.cloud.member.service.MemberService;
import com.cc.cloud.test.common.IntegrationTestBase;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class MemberServiceImplTest extends IntegrationTestBase {
@Autowired
private MemberService memberService;
@Test
public void testAddMember() {
String name = getFaker().name().fullName();
Member member = new Member();
member.setMemberName(name);
Member addMember = memberService.addMember(member);
Assert.assertEquals(name, addMember.getMemberName());
}
@Test
public void testFindAllMembers() {
Assert.assertEquals(1, memberService.findAllMembers().size());
}
}
具体的配置和代码我这里就不多解释了,有些也加了注释,有兴趣的可以参考下面的参考资料。
参考
Integration Tests with @SpringBootTest
Testing Spring MVC Web Controllers with @WebMvcTest
Spring中文文档翻译Integration Testing(1)
springboot Junit单元测试之坑–@SpringBootTest注解无法加载src/main/resources目录下资源文件
SpringBoot2.X (十四): @SpringBootTest单元测试
SpringBoot整合Spring Data JPA、MySQL、Druid并使用Mockito实现单元测试
使用H2Database+Druid连接池+Spring Data JPA+Ehcache实现CRUD操作
How to disable Eureka and Spring Cloud Config in a WebMvcTest?
How to selectively disable Eureka discovery client with Spring?
MockMVC - 基于RESTful风格的SpringMVC的测试
使用com.jayway.jsonpath.JsonPath包进行JSON的快速解析、设置值需要注意的性能提升方法
源代码
https://gitee.com/cckevincyh/spring-cloud-demo/tree/int-test/