首页 > 解决方案 > 使用 mockito.mockStatic 失败的单元测试

问题描述

所以,我正在编写一个 junit 测试,但我似乎无法弄清楚它为什么会失败。我正在使用 Mockito.mockStatic 来模拟 InetAddres.class。一次运行所有单元测试失败。分别运行它们会成功。我知道静态块被初始化一次。我似乎无法弄清楚为什么每个单元测试都没有重新初始化类 Host 。任何帮助表示赞赏

Ĵ

这是我的代码:

import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.net.InetAddress;
import java.net.UnknownHostException;

import static org.assertj.core.api.Assertions.assertThat;


class HostTest {

    @Test
    void testLocalhost() {
        try (MockedStatic<InetAddress> inetAddressMockedStatic = Mockito.mockStatic(InetAddress.class)) {
            InetAddress inetAddress = Mockito.mock(InetAddress.class);
            Mockito.when(inetAddress.getHostName()).thenReturn("LOCALHOST");
            inetAddressMockedStatic.when(InetAddress::getLocalHost).thenReturn(inetAddress);
            assertThat(Host.getLOCALHOST()).isEqualTo("LOCALHOST");
            Mockito.reset(inetAddress);

        }
    }

    @Test
    void testIP() {
        try (MockedStatic<InetAddress> inetAddressMockedStatic = Mockito.mockStatic(InetAddress.class)) {
            InetAddress inetAddress = Mockito.mock(InetAddress.class);
            Mockito.when(inetAddress.getHostAddress()).thenReturn("127.0.0.1");
            inetAddressMockedStatic.when(InetAddress::getLocalHost).thenReturn(inetAddress);
            assertThat(Host.getIP()).isEqualTo("127.0.0.1");
        }
    }

    @Test
    void testUnkownHostExceptionIP() {
        try (MockedStatic<InetAddress> inetAddressMockedStatic = Mockito.mockStatic(InetAddress.class)) {
            inetAddressMockedStatic.when(InetAddress::getLocalHost).thenThrow(UnknownHostException.class);
            assertThat(Host.getIP()).isEqualTo("Unkown ip");
        }

    }

    @Test
    void testUnkownHostExceptionLocalhost() {
        try (MockedStatic<InetAddress> inetAddressMockedStatic = Mockito.mockStatic(InetAddress.class)) {
            inetAddressMockedStatic.when(InetAddress::getLocalHost).thenThrow(UnknownHostException.class);
            assertThat(Host.getLOCALHOST()).isEqualTo("Unkown hostname");

        }
    }
}




import java.net.InetAddress;
import java.net.UnknownHostException;

public class Host {

    private static String LOCALHOST;
    private static String IP;

    static {
        try {
            InetAddress localhost = InetAddress.getLocalHost();
            LOCALHOST = localhost.getHostName();
            IP = localhost.getHostAddress();
        } catch (UnknownHostException e) {
            LOCALHOST = "Unkown hostname";
            IP = "Unkown ip";
        }
    }

    public static String getLOCALHOST() {
        return LOCALHOST;
    }

    public static String getIP() {
        return IP;
    }
}

标签: javaunit-testingstaticmockito

解决方案


静态初始化程序仅在加载类时执行一次。Host这意味着它只会为使用该类的第一个测试用例运行。

在您的示例中,一旦testLocalhost运行,该类将在 line 中使用 Host.getLOCALHOST(),此时其初始化程序已被执行。它永远不会在其余的单元测试中再次运行。

如果你切换这些测试用例的顺序,你会得到不同的结果。

从您的测试用例来看,您可以做一些事情来使代码符合您的期望。由于 IP 和主机名会在您的程序执行期间发生变化,因此它们不应该是在静态初始化程序块中设置的静态成员。

  1. 摆脱共享状态。撇开并发性和内存可见性不谈,static成员对类的所有实例都是可见的。省略static关键字并将它们变成常规字段

    public class Host {
    
        private final String hostName;
    
        private final String ip;
    
        // Constructor, use this to build new instances
        public Host(String hostName, String ip) {
            this.hostName = hostName;
            this.ip = ip;
        }
    
        // No longer static, this is now an instance method
        public getHostName() {
            return this.hostName;
        }
    
        public getIp() {
            return this.ip;
        }
    }
    
  2. 构建类的实例,将参数传递给构造函数以自定义其行为。

    // Host.getIp(); // If IP and host name can vary, don't make them static
    InetAddress localhost = InetAddress.getLocalHost();
    // build a new instance of Host, provide the relevant data at construction time
    Host testedHost = new Host(localhost.getHostName(), localhost.getHostAddress());
    // call the instance method, this doesn't affect your other tests
    assertThat(testedHost.getIp()).is(someIp);
    // at this point, the Host instance you created may be garbage-collected to free memory (you don't need to do that yourself)
    

    现在每个测试用例都将独立于其他测试用例。Host只需在每次需要时创建一个新实例。

  3. 摆脱静态模拟。注意InetAddress方法调用是如何移到Host类之外的。通过将它们传递给构造函数,您可以使代码更易于测试。实现了控制反转

您可以使用工厂方法代替公共构造函数。底线是,如果您想让类改变其行为,通常最好创建新实例并封装任何状态。

静态类和成员更适合诸如在程序执行过程中不会改变的不可变内容,或不依赖任何内部状态的实用方法,即纯函数


推荐阅读