How to mock a method in an ENUM class?

2019-06-16 04:44发布

问题:

I am working on writing JUNIT test case for my below ENUm class. My below class will only give me the hostname for the current machine where I am running my code. While I am writing JUNIT test, how can I mock the below class, so that I can change getHostName() method whenever I want to so that whenever I am calling getDatacenter(), it can return me whatever hostname I am passing by mocking it. I don't want to make it as a parametrized.

I just want to test certain cases while changing the hostname while mocking it.

public enum DatacenterEnum {
    DEV, DC1, DC2, DC3;


    public static String forCode(int code) {
    return (code >= 0 && code < values().length) ? values()[code].name() : null;
    }
    private static final String getHostName() {
        try {
            return InetAddress.getLocalHost().getCanonicalHostName().toLowerCase();
        } catch (UnknownHostException e) {
            s_logger.logError("error = ", e);
        }

        return null;
    }

    public static String getDatacenter() {
        return getHostName();
    }
}

回答1:

It is possible, but this is not recommended, it would be better to refactor the code.

Working example with Mockito/PowerMock

@RunWith(PowerMockRunner.class)
@PrepareForTest(DatacenterEnum.class)
public class DatacenterEnumTest {

    @Mock
    InetAddress inetAddress;

    @Test
    public void shouldReturnDatacenter() throws UnknownHostException {
        //given
        mockStatic(InetAddress.class);
        given(inetAddress.getCanonicalHostName()).willReturn("foo");
        given(InetAddress.getLocalHost()).willReturn(inetAddress);

        //when
        String datacenter = DatacenterEnum.getDatacenter();

        //then
        assertThat(datacenter).isEqualTo("foo");
    }
}

Dependencies

  • org.powermock:powermock-module-junit4:1.5.2
  • org.powermock:powermock-api-mockito:1.5.2
  • org.assertj:assertj-core:1.5.0
  • junit:junit:4.11


回答2:

It's easy with JMockit:

@Test
public void mockInetAddress(@Cascading final InetAddress inetAddress)
{
    new NonStrictExpectations() {{
        inetAddress.getCanonicalHostName(); result = "foo";
    }};

    String datacenter = DatacenterEnum.getDatacenter();

    assertEquals("foo", datacenter);
}

You could also mock the getHostName() method in the enum, of course, but it's best to avoid mocking private methods.



回答3:

You could create a Datacenter interface and have the enum implement the interface. This would make mocking more easy.

Most of all I would not place configuration information in an Enum to begin with. If you ever have to add an other Datacenter (or the config of a Datacenter changes) you have to recompile the code. Consider putting the configuration in a normal class reading for example a java properties file or a XML file. (This function might be already implement in your framework.)

If this is not possible you might use "darkes reflaction" magic to change fields in your Enum to the required values.



回答4:

This is a way you can do it with Mockito/Powermock. You need Powermock, because Mockito is not able to mock static mehtods:

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.junit.Assert.assertEquals;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({DatacenterEnum.class})
public class DatacenterEnumTest {

    @Test
    public void testGetDatacenter() {
        mockStatic(DatacenterEnum.class);
        when(DatacenterEnum.getDatacenter()).thenReturn("YourHostname");

        String datacenter = DatacenterEnum.getDatacenter();

        assertEquals("YourHostname", datacenter);
    }
}

Maven Dependencies

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito</artifactId>
        <version>1.5.2</version>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>1.5.2</version>
    </dependency>
</dependencies>


回答5:

I may be old school, but I'd really refactor the code under test rather than using classloader hacks. Something like:

public enum DatacenterEnum {
    DEV, DC1, DC2, DC3;


    static String hostName = InetAddress.getLocalHost().getCanonicalHostName().toLowerCase();

    public static String getHostName() {
        return hostName;
    }
}

and in your test code, prior to running the test:

DataCenterEnum.hostName = "foo";