I am writing some unit tests and try to cover as much as posible from my code. Now I want to write a test that verifies the name of the local host.
The method looks like this:
public static String getLocalhostName() {
try {
return InetAddress.getLocalHost().getHostName();
}
catch ( final UnknownHostException e ) {
throw new RuntimeException( e.getMessage() );
}
}
and my test:
@Test
public void testGetLocalhostName() {
final String host = getLocalhostName();
Assert.assertEquals( "mycomputer", host );
}
The question is how can I refactor this in order to cover also the catch
block from the main method?
As a rule of thumb, you should not be mocking external libraries. By mocking, you make an assumption how it behaves, what may not be the case or what may change in future. You should only mock code that you control and code that has your tests.
In this case, I would extract InetAddress.getLocalHost().getHostName()
to another class HostNameProvider
and then do dependency injection, just like in John Williams' answer but without using power mock. Then you would have two tests for your current class and one for HostNameProvider
(only happy path):
HostNameProvider
returnsHostNameProvider
throws an exceptionHowever what happens in practice when applying test driven development? You write a test, you write implementation and it doesn't compile so you have to fix it by adding try-catch. At this point I would rely that no developer is so irresponsible to just swallow the exception. And if anyone does, you have static analysis - both IntelliJ and Sonar will highlight this. In short - I wouldn't test it.
I also recommend to modify the templates, so when you generate try-catch, it will automatically add throw new RuntimeException(e);
in the catch.
@Test
public void testGetLocalhostName() {
String url = "http://hostname/xxxxx";
Throwable throwable = catchThrowable(() -> foo.getLocalHost(url));
assertThat(throwable)
.isNotNull()
.isInstanceOf(RuntimeException.class)
.hasMessage("your message");
assertThat(throwable.getCause()).isInstanceOf(UnknownHostException.class);
}
You can test exception type, message and test hostname with this way.
This will get you your test coverage coverage.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.net.InetAddress;
import java.net.UnknownHostException;
import static org.mockito.BDDMockito.given;
@RunWith(PowerMockRunner.class)
@PrepareForTest(InetAddress.class)
public class Mocker {
@Mock
private java.net.InetAddress mockedAddress;
@Test(expected = RuntimeException.class)
public void testGetLocalHostName() throws Exception {
//given
PowerMockito.mockStatic(InetAddress.class);
given(InetAddress.getLocalHost()).willReturn(mockedAddress);
given(mockedAddress.getHostName()).willThrow(UnknownHostException.class);
//when
getLocalhostName();
}
public static String getLocalhostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (final UnknownHostException e) {
throw new RuntimeException(e.getMessage());
}
}
}
But it isn't satisfactory because it doesn't verify that the expected code has run before the RuntimeException
is thrown.
Capturing the exception might be better and then you can verify the calls to the mocked classes.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.net.InetAddress;
import java.net.UnknownHostException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
@RunWith(PowerMockRunner.class)
@PrepareForTest(InetAddress.class)
public class Mocker {
@Mock
private java.net.InetAddress mockedAddress;
@Test
public void testGetLocalHostName() throws Exception {
//given
PowerMockito.mockStatic(InetAddress.class);
given(InetAddress.getLocalHost()).willReturn(mockedAddress);
given(mockedAddress.getHostName()).willThrow(UnknownHostException.class);
//when
try {
getLocalhostName();
} catch (RuntimeException e) {
PowerMockito.verifyStatic();
InetAddress.getLocalHost();
verify(mockedAddress).getHostName();
}
}
public static String getLocalhostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (final UnknownHostException e) {
throw new RuntimeException(e.getMessage());
}
}
}
I came across the same issue, and came up with this:
private static String getHostName() {
// unit test trickery!!!
// the TRY should simply return Inet4Address.getLocalHost().getHostName().
// but, there's no way to force an UnknownHostException, so we can't
// get 100% coverage. 100% coverage isn't entirely necessary, but it
// was the only uncovered line in the entire project, so let's make it happen.
// throw the exception manually and use it to propagate the host name to the
// catch, then the catch returns it. BOOM! Sorcery.
// This is only called once during the app life time, so we can take
// a hit on the unnecessary exception.
try {
throw new UnknownHostException(Inet4Address.getLocalHost().getHostName());
} catch (final UnknownHostException e) {
return e.getMessage();
}
}
It's used exactly once (the result is assigned to a variable, and the variable is reused), so I'm willing to to take the ethical hit.
Of course, if there really is an exception, the distinction is't made. The CATCH could be elaborated to figure that out. (IE: if any spaces in the result, then it's not a host name)
public static String getLocalhostName() throws UnknownHostException {
try {
return InetAddress.getLocalHost().getHostName();
}
catch ( final UnknownHostException e ) {
throw new RuntimeException( e.getMessage() );
}
}
@Test
public void testGetLocalhostName() throws UnknownHostException {
final String host = getLocalhostName();
Assert.assertEquals( "mycomputer", host );
}
and use try..catch in main or declare it to throw Exception or UnKnownHostException
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.