Провал модульного теста с помощью mockito.mockStatic

#java #unit-testing #static #mockito

Вопрос:

Итак, я пишу тест junit и, похоже, не могу понять, почему он терпит неудачу. Я использую Mockito.mockStatic для того, чтобы издеваться InetAddres.class. Запуск модульных тестов сразу завершается неудачей. Запуск их по отдельности завершается успешно. Я понимаю, что статические блоки инициализируются один раз. Чего я, кажется, не могу понять, так это почему хост класса не инициализируется заново с каждым модульным тестом. Любая помощь будет признательна

J

Вот мой код:

 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;
    }
}
 

Ответ №1:

Статический инициализатор выполняется только один раз, когда класс загружен. Это означает, что он будет выполняться только для первого тестового случая с использованием Host класса.

В вашем примере после testLocalhost запуска класс используется в строке 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 класса. Передавая их через конструктор, вы облегчаете тестирование кода. Достигается инверсия управления.

Вместо общедоступного конструктора можно использовать заводской метод. Суть в том, что если вы хотите, чтобы класс изменил свое поведение, обычно лучше создавать новые экземпляры и инкапсулировать любое состояние.

Статические классы и члены лучше подходят для таких вещей, как неизменяемые компоненты, которые не будут изменяться в ходе выполнения вашей программы, или служебные методы, которые не зависят от какого-либо внутреннего состояния, т. е. чистые функции.