PowerMock在单元测试中的应用

关于

配置环境

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <powermock.version>2.0.2</powermock.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

应用

Mock 外部调用

public class ClassUnderTest {
    public void methodToTest(){
        final long id = IdGenerator.generateNewId();
        System.out.println(id);
    }
}

这个待测试的类有一个外部调用 IdGenerator.generateNewId(),为了测试,我们必须把这个外部调用mock掉,返回我们想要的值才能测试。可以通过 mockStatic 实现静态方法的mock,通过when(IdGenerator.generateNewId()).thenReturn(2L) 将静态方法返回的值设为给定的值,代码如下

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest(IdGenerator.class)
public class TestClassUnderTest {
    @Test
    public void demoStaticMethodMocking() throws Exception {
        mockStatic(IdGenerator.class);
        when(IdGenerator.generateNewId()).thenReturn(2L);
        new ClassUnderTest().methodToTest();

    }
}

Mock非静态方法和对象(例如文件IO)

import java.io.File;

public class DirectoryStructure {
    public boolean create(String directoryPath) {
        File directory = new File(directoryPath);

        if(directory.exists()){
            throw new IllegalArgumentException(directoryPath + " already exists.");
        }

        return directory.mkdirs();
    }
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.io.File;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.powermock.api.mockito.PowerMockito.verifyNew;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.whenNew;

@RunWith(PowerMockRunner.class)
@PrepareForTest(DirectoryStructure.class)
public class TestDirectoryStructure {
    @Test
    public void createDirectoryWhenNotExists() throws Exception {
        final String directoryPath = "mocked path";
        File directoryMock = mock(File.class);

        whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock);
        when(directoryMock.exists()).thenReturn(false);
        when(directoryMock.mkdirs()).thenReturn(true);

        assertTrue(new DirectoryStructure().create(directoryPath));
        verifyNew(File.class).withArguments(directoryPath);
    }
}

Mock 私有字段

public class LocalServiceImpl {
    private ServiceA remoteService;

    public Node getRemoteNode(int num) {
        return remoteService.getRemoteNode(num);
    }
}
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.powermock.reflect.Whitebox;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

/**
 * Created by zuoyuan on 2019/7/19.
 */
@RunWith(MockitoJUnitRunner.class)
public class TestLocalServiceImplMock {
    private LocalServiceImpl localService;

    private ServiceA remoteService;

    @Before
    public void setUp(){
        localService = new LocalServiceImpl();
        remoteService = Mockito.mock(ServiceA.class);
        Whitebox.setInternalState(localService, "remoteService", remoteService);

    }

    @Test
    public void testMock(){
        Node target = new Node(1, "target");
        Mockito.when(remoteService.getRemoteNode(1)).thenReturn(target);
        Node result = localService.getRemoteNode(1);

        assertEquals(target, result);
        assertEquals(1, result.getNum());
        assertEquals("target", result.getName());

        Node result2 = localService.getRemoteNode(2);
        assertNull(result2);
    }
}

Mock私有方法

public class CodeWithPrivateMethod {

    public void meaningfulPublicApi() {
        if (doTheGamble("Whatever", 1 << 3)) {
            throw new RuntimeException("boom");
        }
    }

    private boolean doTheGamble(String whatever, int binary) {
        Random random = new Random(System.nanoTime());
        boolean gamble = random.nextBoolean();
        return gamble;
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(CodeWithPrivateMethod.class)
public class CodeWithPrivateMethodTest {

    @Test(expected = RuntimeException.class)
    public void when_gambling_is_true_then_always_explode() throws Exception {
        CodeWithPrivateMethod spy = PowerMockito.spy(new CodeWithPrivateMethod());

        when(spy, method(CodeWithPrivateMethod.class, "doTheGamble", String.class, int.class))
                .withArguments(anyString(), anyInt())
                .thenReturn(true);

        spy.meaningfulPublicApi();
    }
}

Mock 静态field

package com.tracholar.testing;

import java.util.logging.Logger;

public class StaticFieldTest {
    private static Logger logger = Logger.getLogger(StaticFieldTest.class.getName());

    public void doSomething(){
        logger.info("Hello");
    }
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import java.util.logging.Logger;

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.tracholar.testing.StaticFieldTest")
@PrepareForTest(StaticFieldTest.class)
public class TestStaticFieldTest {
    @Test
    public void test1(){
        Whitebox.setInternalState(StaticFieldTest.class, "logger", Mockito.mock(Logger.class));
        StaticFieldTest test = new StaticFieldTest();
        test.doSomething();
    }
}

作业