单元测试Mock

559 阅读7分钟

一、maven依赖

有些数据mock需要额外的工具进行(比如需要mock静态、final方法),如Mockito.MockedStatic,PowerMock等等,文档下面的例子全部都是使用PowerMock进行

maven依赖:

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.7.7</version>
    <scope>test</scope>
</dependency>

其中mockito与PowerMock版本依赖关系为:

二、mock小技巧

1. mock静态方法

为什么mockito无法mock静态方法?

Mockito使用继承的方式实现mock的,用CGLIB生成mock对象代替真实的对象进行执行,为了mock实例的方法,可以在subclass中覆盖它,而static方法是不能被子类覆盖的,所以Mockito不能mock静态方法。

PowerMock如何解决这个问题?

PowerMock有着两个非常重要的依赖,一个是javassist,另外一个就是objenesis。其中javassist是一个修改java字节码的工具包;objenesis是一个绕过构造方法来实例化一个对象的工具包。PowerMock的本质是通过修改字节码来实现对静态和final等方法的mock的

在某一个测试方法被注解@PrepareForTest标注之后,在运行测试用例的时候,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,之后,加载这个测试用例使用到的类,注意这里的话,系统类要除外。PowerMock会依据mock要求,对在注解@PrepareForTest里的class文件进行修改(测试类会自己主动的加入注解当中),以此来满足特殊的mock需求。比如说去除final方法的final标识,在静态方法的最前面加入自己的虚拟实现等等。假如,需要mock的是系统类的final方法和静态方法,PowerMock不能直接修改系统类的class文件,而是去修改调用系统类的class文件,以此来满足mock需求。

使用powerMock步骤:

(1)在被测试类上加上注解:@PrepareForTest(Static.class)

(2)mock:PowerMockito.mockStatic(Static.class);

(3)Mockito.when(Static.firstStaticMethod(param)).thenReturn(value);

被测试类

public class StaticClass {
    public static String str(){
        return "aaa";
    }
}

测试用例

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*","javax.script.*"})
@PrepareForTest({StaticClass.class})
public class MockStatic {
    @Test
    public void testStaticClass(){
        PowerMockito.mockStatic(StaticClass.class);
        PowerMockito.when(StaticClass.str()).thenReturn("bb");
        Assert.assertNotEquals(StaticClass.str(),"aaa");
    }
}

mock带参数的静态方法:

completableFutureMockedStatic.when(() -> CompletableFuture.runAsync(Mockito.any(),Mockito.any())).thenReturn(new CompletableFuture<>());

mock静态方法不执行内部方法:

// 彻底mock某方法(不执行内部语句,public/private都可)
MemberModifier.stub(MemberMatcher.method(TestService.class, "methodName")).toReturn(false);


// 如果含有相同方法名,可以在方法名后继续添加参数的类型,有多少个参数就加多少个
MemberModifier.stub(MemberMatcher.method(TestService.class, "methodName"String.class)).toReturn(false);

2. mock私有方法

被测试类

public class PrivateClass {
    public String str(String ss){
        return privateStr()+ss;
    }
    private String privateStr(){
        return "aaa";
    }
}

测试用例

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*","javax.script.*"})
@PrepareForTest({PrivateClass.class})
public class MockPrivate {
    @InjectMocks
    private PrivateClass privateClass;
    @Test
    public void testPrivate() throws Exception {
        PrivateClass spy = PowerMockito.spy(privateClass);
        // mock本地方法
        PowerMockito.doReturn("qqq").when(spy,"privateStr");
        Assert.assertEquals(spy.str("a"),"qqqa");
    }
}

3. mock final方法

被测试类

public final class FinalClass {
    public final String str() {
        return "aaa";
    }
}

测试用例

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*","javax.script.*"})
@PrepareForTest({FinalClass.class})
public class MockFinal {
    @Test
    public void testFinal(){
        FinalClass mock = PowerMockito.mock(FinalClass.class);
        PowerMockito.when(mock.str()).thenReturn("qqq");
        Assert.assertEquals(mock.str(),"qqq");
    }
}

4. mock 线程池

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*","javax.script.*"})
public class MockAsync {
    @InjectMocks
    private AsyncClass asyncClass;
    @Mock
    private ExecutorService executorService;
    @Test
    public void asyncTest() throws ExecutionException, InterruptedException {
        Future<String> future = new Future<String>() {
            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {
                return false;
            }
            @Override
            public boolean isCancelled() {
                return false;
            }
            @Override
            public boolean isDone() {
                return false;
            }
            @Override
            public String get() throws InterruptedException, ExecutionException {
                return "sss";
            }
            @Override
            public String get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
                return null;
            }
        };
        //数据mock
        c
        // 实际方法调用
        String s = asyncClass.doInvoke();
        Assert.assertEquals(s,"sss");
    }

5. Apollo Mock

方式一:直接mock掉Apollo的静态方法,使其使用本地配置方法

@BeforeClass
public static void mockApollo(){
    PowerMockito.mockStatic(EnvUtils.class);
    when(EnvUtils.transformEnv(any())).thenReturn(Env.LOCAL);
}

这种方式的运行原理本质上是mock掉源码的调用流程,使其直接使用本地配置

方式二:修改环境属性

可以通过设置环境属性 apollo.env=Local (注意大小写)

在调用阿波罗配置的时候,不会去远程加载,直接本地使用默认值

* 如:

* ConfigService.getAppConfig().getProperty("key","defaultValue")

* 直接获取到的值为“defaultValue”

public class TestBase {
    @BeforeClass
    public static void initApolloLocalConfig(){
        /**
         * 初始化本地化 阿波罗配置中心,不进行网络请求
         * 在调用阿波罗配置的时候,就会使用默认值
         * 如:
         * ConfigService.getAppConfig().getProperty("key","defaultValue")
         * 直接获取到的值为“defaultValue”
         */
         System.setProperty("apollo.env","Local");
    }
}

但是有时候我们不仅仅只是使用apollo配置中心的默认值,还需要对配置中心返回值进行配置,如果执行测试用例时,不希望使用代码中的默认值,希望使用自己定义的配置值,参考链接中的配置方式。

注:建议将以上几种方式都写在@BeforeClass标注的方法中,如果写在@Before标注的方法中,可能存在以下几种问题:

1.每次运行一个测试用例,都会执行@Before方法,实际上是没有必要的

2.如果被测试类在属性初始化或者静态方法中使用了Apollo配置,@Before无法生效

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*","javax.script.*"})
public class ThreadServiceTest {


    // 测试类运行时,会初始化ThreadService,然后将@Mock标注的属性注入到ThreadService 中,在初始化中如果有静态方法用到了Apollo配置或者像例子中实例化一个属性用到Apollo配置都会导致再次拉取Apollo
    @InjectMocks
    private ThreadService threadService;


    @Mock
    private ThreadDetailInterveneCache threadDetailInterveneCache;


    @Mock
    private ThreadRecommendService threadRecommendService;


    @Mock
    private ThreadParser threadParser;


    @Before
    public static void baseMockApollo(){
      System.setProperty("apollo.env","Local");
    }
}


------------------------------------------------------
@Service
public class ThreadService {
  private ExecutorService batchGetThreadExecutorService = new TraceExecutorService(new ThreadPoolExecutor(getCoreThreadNum(), getMaxThreadNum(), getThreadAliveTime(), TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(getQueueCapacity()), threadFactory, new ThreadPoolExecutor.CallerRunsPolicy()));


    private static int getThreadAliveTime() {
        return ConfigService.getAppConfig().getIntProperty("tribe.batch.get.thread.time", 120);
    }


    private static int getCoreThreadNum() {
        return ConfigService.getAppConfig().getIntProperty("tribe.batch.get.thread.core.thread.num", 16);
    }


    private static int getQueueCapacity() {
        return ConfigService.getAppConfig().getIntProperty("tribe.batch.get.thread.queue.capacity", 1000);
    }


    private static int getMaxThreadNum() {
        return ConfigService.getAppConfig().getIntProperty("tribe.batch.get.thread.max.num", 16);
    }
}

但是有时候我们不仅仅只是使用apollo配置中心的默认值,还需要对配置中心返回值进行配置,按自定义的配置值来跑用例

第一步:在设置环境属性

@RunWith(PowerMockRunner.class)

@PowerMockIgnore({"javax.management.*","javax.script.*"})

public class DemoTest extends TestBase {

// 该单元测试类中需要自定义的配置内容, 可以直接通过原来的方式获取到该内容

@Before

public void initApolloConfig(){

System.setProperty("对应阿波罗配置中心的key","阿波罗配置中心的value");

System.setProperty("对应阿波罗配置中心的key","阿波罗配置中心的value");

System.setProperty("对应阿波罗配置中心的key","阿波罗配置中心的value");

}

@Test

public void testMethod(){

// do test things

}

}

方式三:mock apollo

//mock Apollo
Config application = mock(Config.class);
when(application.getIntProperty(eq("splash.batch.getBuoy.left.time"), anyInt())).thenReturn(500);
PowerMockito.mockStatic(ConfigService.class);
when(ConfigService.getAppConfig()).thenReturn(application);

6. mock Dubbo服务调用

1.mock dubbo服务的异步调用

dubbo服务调用实质也是对于静态方法的mock

被测试类

@Service
public class MessageEngineService {
    private static final Logger logger = LoggerFactory.getLogger(MessageEngineService.class);
    @Autowired
    private MessageEngineFacade messageEngineFacade;


    /**
     * 创建消息主体内容
     **/
    public MessageContentDto createMessageContent(MessageContentDto messageContentDto){
        if(messageContentDto == null){
            logger.warn("MessageEngineService.createMessageContent param is empty. messageContentDto:{}", JSON.toJSONString(messageContentDto));
            return null;
        }
        try {
            messageEngineFacade.createMessageContent(messageContentDto);
            CompletableFuture<ResultDto<MessageContentDto>> future = RpcContext.getContext().getCompletableFuture();
            ResultDto<MessageContentDto> result = future.get();
            if(result.isSuccess()){
                return result.getT();
            }
            logger.info("createMessageContent result failed. messageContentDto:{}",JSON.toJSONString(messageContentDto));
            return null;
        }catch (Exception e){
            logger.error("MessageEngineService.createMessageContent error,messageContentDto:{},e={}", JSON.toJSONString(messageContentDto), e);
            return null;
        }
    }
}

测试类

@PrepareForTest({RpcContext.class})
public class MessageEngineServiceTest extends BaseMock {


    @InjectMocks
    private MessageEngineService messageEngineService;


    @Mock
    private RpcContext rpcContext;


    @Mock
    private MessageEngineFacade messageEngineFacade;


    @Test
    public void createMessageContent() throws ExecutionException, InterruptedException {
        PowerMockito.doReturn(null).when(messageEngineFacade).createMessageContent(Mockito.any());
        // mock 异步调用
        PowerMockito.mockStatic(RpcContext.class);
        PowerMockito.when(RpcContext.getContext()).thenReturn(rpcContext);
        MessageContentDto messageContentDto = new MessageContentDto();
        messageContentDto.setBody("aaa");
        ResultDto resultDto = new ResultDto();
        resultDto.setT(messageContentDto);
        resultDto.setSuccess(true);
        PowerMockito.doReturn(buildCompletableFuture(resultDto)).when(rpcContext).getCompletableFuture();
        MessageContentDto messageContent = messageEngineService.createMessageContent(new MessageContentDto());
        Assert.assertEquals(messageContent.getBody(),"aaa");
    }


    private <T> CompletableFuture<T> buildCompletableFuture(T value){
        return CompletableFuture.completedFuture(value);
    }
}

示例二:

private AppCatInfo appRemote(Long appId) {
    AppCatInfo info = null;
    try {
        appCatService.queryAppCatInfo(null, appId);
        if (info == null) {
            Future<AppCatInfo> future = RpcContext.getContext().getFuture();
            info = future.get();
        }
    } catch (Exception e) {
        logger.error("获取资源信息出错, appId:{}", appId, e);
    }
    return info;
}

测试类:

package com.oppo.cdo.commerce.core.manager;


import org.apache.dubbo.rpc.RpcContext;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;


import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;


import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;


@RunWith(PowerMockRunner.class)
@PrepareForTest({RpcContext.class})
@PowerMockIgnore({"javax.management.*", "javax.script.*"})
public class CatManagerTest extends BaseJunitCase {
    @InjectMocks
    private CatManager catManager;


    @Mock
    private RpcContext rpcContext;


    @Mock
    private AppCatService appCatService;


    @Before
    public void init() throws Exception {
        PowerMockito.doReturn(null).when(appCatService).queryAppCatInfo(any(), anyLong());
        // mock 异步调用
        PowerMockito.mockStatic(RpcContext.class);
        PowerMockito.when(RpcContext.getContext()).thenReturn(rpcContext);
        PowerMockito.doReturn(buildFuture(builderAppCatInfo())).when(rpcContext).getFuture();
    }


    @Test
    public void testAppRemote() throws Exception {
        AppCatInfo appCatInfo = Whitebox.invokeMethod(catManager, "appRemote", 1L);
        System.out.println(appCatInfo);
        Assert.assertEquals(1L, appCatInfo.getAppId());
    }


    private <T> Future<T> buildFuture(T value){
        return CompletableFuture.completedFuture(value);
    }


    private AppCatInfo builderAppCatInfo() {
        AppCatInfo appCatInfo = new AppCatInfo();
        appCatInfo.setAppId(1L);
        return appCatInfo;
    }
}

示例三:

Optional<Resource>> call = Rpc.call(service.find(id));

@PrepareForTest({Rpc.class})
PowerMockito.doReturn(null).when(service).find(anyLong());
PowerMockito.mockStatic(Rpc.class);
PowerMockito.when(Rpc.call(any())).thenReturn(Optional.of(new Resource()));

2.mock dubbo服务的同步调用

同步调用时跟一般mock方式是一样的

@Test
public void syncCreateMessageContentTest(){
    MessageContentDto messageContentDto = new MessageContentDto();
    messageContentDto.setBody("aaa");
    ResultDto resultDto = new ResultDto();
    resultDto.setT(messageContentDto);
    resultDto.setSuccess(true);
    PowerMockito.doReturn(resultDto).when(messageEngineFacade).createMessageContent(Mockito.any());
    MessageContentDto messageContent = messageEngineService.syncCreateMessageContent(new MessageContentDto());
    Assert.assertEquals(messageContent.getBody(),"aaa");
}

7. 匹配对象参数

应用场景

public void provideColumnAccountDtos(String userId, String sender) {
    // 请求参数由程序内部自己new出来的
    MessageHomeReq messageHomeReq = new MessageHomeReq();
    messageHomeReq.setSenders(Lists.newArrayList(sender));
    messageHomeReq.setUserId(userId);
    List<MessageHomeDto> messageHomeDtos = messageEngineService.messageHome(messageHomeReq);


}

方式一:参数匹配器(any)

PowerMockito.when(messageEngineService.messageHome(Mockito.any())).thenReturn(value)


// 被测试方法内多次调用mock方法,第一次返回value1,第二次返回value2
PowerMockito.when(messageEngineService.messageHome(Mockito.any())).thenReturn(value1, value2)

方式二:参数匹配器(eq)

PowerMockito.doReturn(Lists.newArrayList(messageHomeDto)).when(messageEngineService).messageHome(Mockito.eq(messageHomeReq));

注:如果是复杂对象,使用eq时需要参数对象实现equals、hashCode方法

8.mapStruct 与 mockito

通过mapStruct 编写的dto转换类实质上还是一种工具类,但为了使用方便,在项目中通常会通过spring的方式进行依赖注入

如果我们希望测试的时候不用mock这种dto转换类数据,直接走实际流程,怎么操作呢?

9.mock post请求

@RunWith(PowerMockRunner.class)
@PrepareForTest(ConfigService.class)
@PowerMockIgnore({"javax.management.*"})
public class RiskUtilTest {
    private RiskUtil riskUtil = new RiskUtil();


    @Before
    public void init() throws Exception {
        HttpClient httpClient = mock(HttpClient.class);
        when(httpClient.post(anyString(), anyMap(), anyString(), any())).thenReturn(getResult());
        ReflectionTestUtils.setField(riskUtil, "httpClient", httpClient, HttpClient.class);
    }


    private String getResult() {
//        return "{\"result\":{\"score\":90.0,\"riskLevel\":\"DANGER\",\"firedRules\":[\"SSOID_GLOBAL_LIST\"],\"explanations\":[\"全局风险名单\"],\"rulePoint\":\"SSOID\"},\"suc\":true,\"code\":1001,\"message\":\"\"}";
//        return "{\"result\":{\"score\":79.0,\"riskLevel\":\"HIGH\",\"firedRules\":[\"SSOID_GLOBAL_LIST\"],\"explanations\":[\"全局风险名单\"],\"rulePoint\":\"SSOID\"},\"suc\":true,\"code\":1001,\"message\":\"\"}";
        return "{\"result\":{\"score\":0.0,\"riskLevel\":\"LOW\",\"firedRules\":[],\"explanations\":[\"\"],\"rulePoint\":\"\"},\"suc\":true,\"code\":1001,\"message\":\"\"}";
    }




    @Test
    public void testRiskReport() {
        RiskConfig riskConfig = mockRiskConfig(RiskUtil.ADD_POINT);
        System.out.println("风控配置文件:" + JSON.toJSONString(riskConfig));


        //获取风控结果
        System.out.println("风控返回结果:" + riskUtil.getRiskReport(mockPageRequest(), riskConfig));
    }


    private RiskConfig mockRiskConfig(int type) {
        String riskConfigStr = Constants.getRiskConfig();
        List<RiskConfig> riskConfigList = JSONObject.parseArray(riskConfigStr, RiskConfig.class);
        Map<Integer,RiskConfig> tempMap = riskConfigList.stream().collect(Collectors.toMap(RiskConfig::getType, Function.identity(),(key1, key2)->key2));
        return tempMap.get(type);
    }


}

10.mock线程池

public class MyService {  
    public CompletableFuture<Void> doAsyncTask() {  
        return CompletableFuture.runAsync(() -> {  
            // 异步执行任务  
            System.out.println("Running task in background...");  
        });  
    }  
}

import org.junit.jupiter.api.Test;  
import org.mockito.Mockito;  
import java.util.concurrent.CompletableFuture;  
import java.util.concurrent.TimeUnit;  
import static org.junit.jupiter.api.Assertions.*;  
import static org.mockito.Mockito.*;  
  
public class MyServiceTest {  
    @Test  
    public void testRunAsync() throws Exception {  
        // 创建模拟对象  
        MyService myService = Mockito.mock(MyService.class);  
        doNothing().when(myService).doAsyncTask(); // 模拟 doAsyncTask 方法不执行任何操作  
  
        // 调用被模拟的方法  
        CompletableFuture<Void> future = myService.doAsyncTask();  
  
        // 验证方法被调用  
        verify(myService, times(1)).doAsyncTask();  
  
        // 等待异步任务完成,并检查返回的 CompletableFuture 是否正确完成  
        assertTrue(future.isDone()); // 异步任务应该已经完成  
        assertTrue(future.isCompletedExceptionally()); // 由于我们模拟了 doAsyncTask 不执行任何操作,所以应该完成异常  
    }  
}

在这个示例中,我们使用 Mockito 来模拟 MyService 类,并使用 doNothing() 方法来指定 doAsyncTask() 方法不执行任何操作。然后,我们调用 doAsyncTask() 方法并获取返回的 CompletableFuture 对象。最后,我们使用 verify() 方法来验证 doAsyncTask() 方法被正确地调用了一次,并使用 isDone()isCompletedExceptionally() 方法来检查异步任务的完成状态。

在 Mockito 的 verify() 方法中,times(1) 表示验证被模拟的方法 doAsyncTask() 确实被调用了 1 次。如果你使用 times(0),那么表示验证被模拟的方法没有被调用。

在这个示例中,我们使用 times(1) 来验证 doAsyncTask() 方法确实被调用了,因为我们期望异步任务完成并返回一个表示异常完成的状态。如果没有使用 verify() 方法,或者使用 times(0),那么测试将会失败,因为这将意味着 doAsyncTask() 方法没有被调用。

三、编写测试用例小知识点

1.参数化测试

(1)Junit4 参数化测试

官方文档:参数化测试

步骤:

  1. 在测试类上加上注解@RunWith(Parameterized.class)

  2. 构造参数

    @Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][] {
    { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 } }); }

3.参数注入

3.1 通过构造函数注入

@RunWith(Parameterized.class)
public class MockParams1 {
    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {
                { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }
        });
    }
    private int value1;
    private int value2;
    public MockParams1(int value1, int value2) {
        this.value1 = value1;
        this.value2 = value2;
    }
    @Test
    public void test(){
        System.out.println(value1 + value2);
    }
}

3.2 通过参数注入

@RunWith(Parameterized.class)
public class MockParams {
   // 需要是public static修饰
    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {
                { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }
        });
    }
    // public
    @Parameterized.Parameter
    public int value1;
    @Parameterized.Parameter(1)
    public int value2;
    @Test
    public void test(){
        System.out.println(value1 + value2);
    }
}

(2)Junit5参数化测试

文档:junit.org/junit5/docs…

Junit5的参数化测试功能更加强大,可以支持枚举、方法、csv文件作为数据源

@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
    assertTrue(argument > 0 && argument < 4);
}

2.thenReturn与doReturn

在Mockito中打桩(即stub)有两种方法when(...).thenReturn(...)和doReturn(...).when(...)。这两个方法在大部分情况下都是可以相互替换的,但是在使用了Spies对象(@Spy注解),而不是mock对象(@Mock注解)的情况下他们调用的结果是不相同的

● when(...) thenReturn(...)会调用真实的方法,如果你不想调用真实的方法而是想要mock的话,就不要使用这个方法。

● doReturn(...) when(...) 不会调用真实方法

3.参数匹配器

使用参数匹配器时,所有参数都应使用匹配器。

verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// 不正确的,第三个参数没有使用参数匹配器
verify(mock).someMethod(anyInt(), anyString(), "third argument");

如果参数是null值,需要用mockito.eq 进行参数匹配,不能使用类似mockito.any()这种

四、常见问题与解决方式

1.测试用例报错:org.mockito.exceptions.misusing.WrongTypeOfReturnValue:

将when(test.do()).thenReturn(mockData)改为

doReturn(mockData).when(test).do()形式

2.**错误:**ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider jdk.nashorn.api.scripting.NashornScriptEngineFactory not a subtype ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider jdk.nashorn.api.scripting.NashornScriptEngineFactory not a subtype 2017-06-09 13:37:24,660 main ERROR No ScriptEngine found for language javascript. Available languages are: 2017-06-09 13:37:24,776 main WARN No script named {} could be found

原因:主要由于PowerMock与一些组件,比如log4j有冲突,但不影响测试用例的执行,可以直接忽略

解决:在测试类上加上注解

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*","javax.script.*"})
public class MockUtils {


}

3.Junit5 中mock 静态方法

junit5不支持PowerMockito,但可以使用MockedStatic.

注:Mockito3.4.0 版本之前是不支持MockedStatic的

@Test
public void staticTest(){
    MockedStatic<Static5Class> static5ClassMockedStatic = Mockito.mockStatic(Static5Class.class);
    static5ClassMockedStatic.when(Static5Class::str).thenReturn("bbb");
    Assert.assertEquals("bbb",Static5Class.str());
}

五、JUNIT5与Mocktio3

1.How to resolve Unneccessary Stubbing exception

在一段代码中,可能会存在多段代码逻辑分支。

我们将所有的外部依赖方法都mock之后,如果在Test流程中因为某个逻辑分支没有使用到,Mockito会报不必要的存根错误。

如下图所示,我写了6个方法的mock,但是其实在Test流程中,因为只跑了某个逻辑分支,该分支只用到了其中的4个mock。就会报错。

解决方法:

@MockitoSettings(strictness = Strictness.LENIENT)
class MyTest{
  ......
}

2.Mockito3 中必须手动关闭mockStatic

错误描述:图片:

解决方案:每个测试用例调用完成MockedStatic rpcContextMockedStatic = Mockito.mockStatic(RpcContext.class);之后必须手动关闭:

rpcContextMockedStatic.close();