文章目录
Spring Boot Test单元测试环境搭建
- 首先我用到了PowerMock和Mockito。所以先加入PowerMock和Mockito的依赖。PowerMock和Mockito的版本使用好像有很多坑,反正我试了很多个版本,下面的配置对我来说是起作用并且没有报错的。
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.8.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
Service层的单元测试
Service层的单元测试比较简单,因为一般涉及到的都是我们的主要业务逻辑代码。
首先我们先抽取一个单元测试的基类UnitTestBase。
package com.cc.cloud.unit.test.common;
import com.github.javafaker.Faker;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.runners.MethodSorters;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Ignore
public abstract class UnitTestBase {
private Faker faker;
public Faker getFaker() {
return this.faker;
}
@Before
public void initUnitTestBase() {
this.faker = new Faker();
}
}
基于这个UnitTestBase类,我们再创建一个Service层单元测试的基类。
package com.cc.cloud.unit.test.common;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@Ignore
public abstract class ServiceUnitTestBase extends UnitTestBase{
}
下面开始编写Service的单元测试
package com.cc.cloud.member.service.impl;
import com.cc.cloud.member.domain.Member;
import com.cc.cloud.member.repository.MemberRepository;
import com.cc.cloud.unit.test.common.ServiceUnitTestBase;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import java.util.ArrayList;
import java.util.List;
import static org.mockito.Mockito.when;
@PrepareForTest(MemberServiceImpl.class)
public class MemberServiceImplUnitTest extends ServiceUnitTestBase {
@InjectMocks
private MemberServiceImpl memberService;
@Mock
private MemberRepository memberRepository;
@Test
public void should_return_member_list_when_call_findAllMembers(){
//Given
String memberName = getFaker().name().fullName();
List<Member> members = getMembers(memberName);
when(memberRepository.findAll()).thenReturn(members);
//When
List<Member> allMembers = memberService.findAllMembers();
//Then
Assert.assertEquals(1,allMembers.size());
Assert.assertEquals(memberName,allMembers.get(0).getMemberName());
}
private List<Member> getMembers(String memberName) {
List<Member> members = new ArrayList<>();
Member member = getMember(memberName);
members.add(member);
return members;
}
private Member getMember(String memberName) {
Member member = new Member();
member.setMemberName(memberName);
return member;
}
}
Controller层的单元测试
这里跟Service层的单元测试就不一样了,我们需要用到@WebMvcTest的注解。
@WebMvcTest注解主要用于controller层测试,只覆盖应用程序的controller层,HTTP请求和响应是Mock出来的,因此不会创建真正的连接。因此需要创建 MockMvc bean进行模拟接口调用。
如果Controller层对Service层中的其他bean有依赖关系,那么需要使用Mock提供所需的依赖项。
WebMvcTest要快得多,因为我们只加载了应用程序的一小部分。
同样的,我们先抽取Controller层单元测试的基类
package com.cc.cloud.unit.test.common;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import jdk.nashorn.internal.ir.annotations.Ignore;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@WebAppConfiguration
@WebMvcTest
@Ignore
public abstract class ControllerUnitTestBase extends UnitTestBase {
@Autowired
private MockMvc mockMvc;
private ObjectMapper jsonMapper;
@Before
public void initControllerUnitTest() {
this.jsonMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
public MockMvc getMockMvc() {
return this.mockMvc;
}
public ObjectMapper getJsonMapper() {
return this.jsonMapper;
}
}
但是在运行单元测试的时候报错如下:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.1.RELEASE)
2019-11-09 14:55:34.429 INFO [cloud-service-member,,,] 4172 --- [ main] c.c.c.m.c.MemberControllerUnitTest : The following profiles are active: test
2019-11-09 14:55:35.271 INFO [cloud-service-member,,,] 4172 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'com.cc.cloud.member.feign.OrderFeign' of type [org.springframework.cloud.openfeign.FeignClientFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-11-09 14:55:35.348 INFO [cloud-service-member,,,] 4172 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$8fdb9b79] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-11-09 14:55:35.531 WARN [cloud-service-member,,,] 4172 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaAuditingHandler': Cannot resolve reference to bean 'jpaMappingContext' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
2019-11-09 14:55:35.533 INFO [cloud-service-member,,,] 4172 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-11-09 14:55:35.540 ERROR [cloud-service-member,,,] 4172 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaAuditingHandler': Cannot resolve reference to bean 'jpaMappingContext' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:378) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:110) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:662) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:188) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1308) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1154) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863) ~[spring-context-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) ~[spring-context-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.1.RELEASE.jar:2.1.1.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.1.RELEASE.jar:2.1.1.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.1.RELEASE.jar:2.1.1.RELEASE]
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127) [spring-boot-test-2.1.1.RELEASE.jar:2.1.1.RELEASE]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) [spring-test-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.junit.runner.JUnitCore.run(JUnitCore.java:137) [junit-4.12.jar:4.12]
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) [junit-rt.jar:na]
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) [junit-rt.jar:na]
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) [junit-rt.jar:na]
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) [junit-rt.jar:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1745) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:576) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:367) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
... 44 common frames omitted
Caused by: java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
解决方法就是:
把我们@EnableJpaAuditing的注解从启动类中移动到配置类中。
package com.cc.cloud.member;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication(scanBasePackages = "com.cc.cloud")
@EnableEurekaClient
@EnableFeignClients
@EnableCircuitBreaker
@EnableTransactionManagement // 开启注解事务管理
public class MemberApp {
public static void main(String[] args) {
SpringApplication.run(MemberApp.class, args);
}
}
package com.cc.cloud.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@EnableJpaAuditing
@Configuration
public class AuditorConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return new AuditorProvider();
}
}
参考:Spring @WebMvcTest with @EnableJpa* annotation
但是启动测试之后又报了另一个错误。
2019-11-09 15:05:40.854 INFO [cloud-service-member,,,] 12648 --- [ main] c.c.c.m.c.MemberControllerUnitTest : The following profiles are active: test
2019-11-09 15:05:41.588 INFO [cloud-service-member,,,] 12648 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'com.cc.cloud.member.feign.OrderFeign' of type [org.springframework.cloud.openfeign.FeignClientFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-11-09 15:05:41.672 INFO [cloud-service-member,,,] 12648 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$39368a96] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-11-09 15:05:42.903 INFO [cloud-service-member,,,] 12648 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-11-09 15:05:43.793 INFO [cloud-service-member,,,] 12648 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2019-11-09 15:05:43.794 INFO [cloud-service-member,,,] 12648 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2019-11-09 15:05:43.843 INFO [cloud-service-member,,,] 12648 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 48 ms
2019-11-09 15:05:44.256 INFO [cloud-service-member,,,] 12648 --- [ main] c.c.c.m.c.MemberControllerUnitTest : Started MemberControllerUnitTest in 13.633 seconds (JVM running for 16.397)
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: No Scope registered for scope name 'refresh'
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1013)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:71)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:166)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:182)
at com.cc.cloud.member.controller.MemberControllerUnitTest.testFindAllMembers(MemberControllerUnitTest.java:16)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
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.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.IllegalStateException: No Scope registered for scope name 'refresh'
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:350)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:672)
at com.cc.cloud.member.controller.MemberController$$EnhancerBySpringCGLIB$$bfb0c901.findAllMembers(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
... 49 more
解决方法是ControllerUnitTestBase类中加入@ImportAutoConfiguration(RefreshAutoConfiguration.class)
当然也不要忘记在cloud-test-common module中加入对应的依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<scope>provided</scope>
</dependency>
解释如下:
Spring Cloud uses RefreshAutoConfiguration to add the refresh scope to your application. By default, this auto-configuration isn’t included in the auto-configuration that’s enabled by @WebMvcTest. You can enable extra auto-configuration by adding @ImportAutoConfiguration(RefreshAutoConfiguration.class) to your tests.
参考:@RunWith and @WebMvcTest: No Scope registered for scope name ‘refresh’ during Controller tests
所以最后的ControllerUnitTestBase代码如下:
package com.cc.cloud.unit.test.common;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import jdk.nashorn.internal.ir.annotations.Ignore;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@ImportAutoConfiguration(RefreshAutoConfiguration.class)
@WebAppConfiguration
@WebMvcTest
@Ignore
public abstract class ControllerUnitTestBase extends UnitTestBase {
@Autowired
private MockMvc mockMvc;
private ObjectMapper jsonMapper;
@Before
public void initControllerUnitTest() {
this.jsonMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
public MockMvc getMockMvc() {
return this.mockMvc;
}
public ObjectMapper getJsonMapper() {
return this.jsonMapper;
}
}
下面是Controller层单元测试的代码:
package com.cc.cloud.member.controller;
import com.cc.cloud.member.domain.Member;
import com.cc.cloud.member.feign.OrderFeign;
import com.cc.cloud.member.service.MemberService;
import com.cc.cloud.unit.test.common.ControllerUnitTestBase;
import org.junit.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import java.util.ArrayList;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
public class MemberControllerUnitTest extends ControllerUnitTestBase {
@MockBean
private MemberService memberService;
@MockBean
private OrderFeign orderFeign;
@Test
public void should_return_success_response_when_request_url_member() throws Exception {
//Given
String memberName = getFaker().name().fullName();
List<Member> members = getMembers(memberName);
when(memberService.findAllMembers()).thenReturn(members);
//When
//Then
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("成功"))
.andExpect(jsonPath("$.result[0].memberName").value(memberName));
}
private List<Member> getMembers(String memberName) {
List<Member> members = new ArrayList<>();
Member member = getMember(memberName);
members.add(member);
return members;
}
private Member getMember(String memberName) {
Member member = new Member();
member.setMemberName(memberName);
return member;
}
}
Repository层的测试
这里跟Controller层和Service层也是不一样的,我们需要用到@DataJpaTest的注解。
为了测试Spring Data JPA,或者任何其他与JPA相关的组件,Spring Boot提供了@DataJpaTest注释。
首先我们还是抽取Repository层的单元测试基类RepositoryUnitTestBase。
package com.cc.cloud.unit.test.common;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringRunner.class)
@ActiveProfiles({"test"})
@DataJpaTest
@Transactional
@Ignore
public abstract class RepositoryUnitTestBase extends UnitTestBase{
}
然后就是我们Repository层的单元测试代码
package com.cc.cloud.member.repository;
import com.cc.cloud.member.domain.Member;
import com.cc.cloud.unit.test.common.RepositoryUnitTestBase;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class MemberRepositoryTest extends RepositoryUnitTestBase {
@Autowired
private MemberRepository memberRepository;
@Test
public void should_return_member_list_when_call_findAll(){
//When
List<Member> memberList = memberRepository.findAll();
//Then
Assert.assertEquals(1,memberList.size());
}
}
测试环境配置bootstrap-test.yml文件
最后给出我的测试环境配置。与之前的集成测试的配置是一样的。
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
参考
@RunWith and @WebMvcTest: No Scope registered for scope name ‘refresh’ during Controller tests
SpringBoot @WebMvcTest, autowiring RestTemplateBuilder
springboot @WebMvcTest测试controller报No qualifying bean of type 错误
使用MockMvc与SpringBootTest和使用WebMvcTest之间的区别
Spring @WebMvcTest with @EnableJpa* annotation
spring Boot integration test with WebMvcTest
学习 Spring Boot:(二十九)Spring Boot Junit 单元测试
Android最佳Mock单元测试方案:Junit + Mockito + Powermock
Testing Spring MVC Web Controllers with @WebMvcTest
Testing JPA Queries with @DataJpaTest
使用 @MockBean 和 @SpyBean 解决 SpringBoot 单元测试中 Mock 类装配的问题
MockMVC - 基于RESTful风格的SpringMVC的测试
源代码
https://gitee.com/cckevincyh/spring-cloud-demo/tree/unit-test/