12

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(); } } 
3
  • 1
    Do those method have to be in the enum class? There doesn't appear to be any references between the enum values and those methods. Commented Jan 18, 2014 at 10:01
  • I have removed those information from the code.. There are couple of methods which are using those details accordingly from the hostname.. So my main center of attraction was mocking the getHostName method, around which other codes revovlve.. Commented Jan 18, 2014 at 10:05
  • stackoverflow.com/a/9722244/516167 Commented Jan 18, 2014 at 11:18

5 Answers 5

3

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
Sign up to request clarification or add additional context in comments.

2 Comments

What do the imports look like?
Why is it not recommended? How should this be refactored?
2

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.

Comments

1

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.

Comments

0

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> 

Comments

0

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

3 Comments

This will only partially work. The rules of the ClassLoader are to load a class on it's first reference, which entails static initializers. The OP has an UnknownHostException being thrown, and now that you've moved it to a static field initialization, that initialization now has the chance for an exception to propagate up to the ClassLoader. Your future assignment to hostName will result in a NoClassDefFoundError, if some sort of checked or unchecked exception were to be thrown. It's not a good idea to put methods that throw exceptions in unchecked initializers.
Perhaps I was overzealous in removing that catch block, but I could not imagine a case where the local host name was not known to DNS? But yes, if you actually see this exception thrown, I'd move the lookup into a dedicated static method and add a catch block, so the class can correctly initialize and the test code gets a chance to override the host name.
Well it's the InetAddress#getLocalHost() function itself that throws the exception, and the javadocs only seem to indicate if the local host name could not be resolved into an address. But in fairness, I didn't really think past that point myself to think why that could occur. But it does happen in cases: stackoverflow.com/questions/1881546/… . I'ts more that I'm just of the mindset of defensive programming for decreased maintenance.