【Spring Cloud 基础设施搭建系列】Spring Cloud Demo项目 Spring Boot Test单元测试环境搭建

Spring Boot Test单元测试环境搭建

  1. 首先我用到了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 错误

WebMvcTest与SpringBootTest

使用MockMvc与SpringBootTest和使用WebMvcTest之间的区别

第三十五章:SpringBoot与单元测试的小秘密

Spring @WebMvcTest with @EnableJpa* annotation

spring Boot integration test with WebMvcTest

PowerMock踩坑指南

学习 Spring Boot:(二十九)Spring Boot Junit 单元测试

Junit - 忽略测试(Ignore Test)

PowerMock单元测试踩坑与总结

Android最佳Mock单元测试方案:Junit + Mockito + Powermock

@MockBean 危害

Unit Testing with Spring Boot

Testing Spring MVC Web Controllers with @WebMvcTest

Testing JPA Queries with @DataJpaTest

Mockito和PowerMock用法

Spring 使用Junit的MockMvc 写测试用例

使用 @MockBean 和 @SpyBean 解决 SpringBoot 单元测试中 Mock 类装配的问题

MockMVC - 基于RESTful风格的SpringMVC的测试

源代码

https://gitee.com/cckevincyh/spring-cloud-demo/tree/unit-test/

发布了647 篇原创文章 · 获赞 816 · 访问量 98万+

猜你喜欢

转载自blog.csdn.net/cckevincyh/article/details/102988887
今日推荐