Reputation: 155
My method returns an exception if the input String (StringBuilder in my case) is longer than 2048 characters. Since we cannot mock final classes using Mockito, can anyone tells me how do unit test this.
One way is to actually pass a string that long which seems stupid. Is there an easy way to test this?
Upvotes: 3
Views: 9834
Reputation:
IMHO, no need for Mockito here, just use something that creates a known-length String
(here I use commons-lang RandomStringUtils
). I also use ExpectedException
to test for exceptions
public class MyClassTest {
private static int LIMIT = 2048;
private static String TEST_STRING1 = RandomStringUtils.random(LIMIT);
private static String TEST_STRING2 = RandomStringUtils.random(LIMIT + 1);
@Rule
public ExpectedException ee = ExpectedException.none();
private MyClass myClass = new MyClass();
@Test
public void smallStringShouldBeOk() {
myClass.myMethod("foobar"); // OK if no exception or assert on a returned value
}
@Test
public void edgeCaseStringShouldThrow() {
ee.expect(SomeException.class)
ee.expectMessage("some message");
myClass.myMethod(TEST_STRING1);
}
@Test
public void tooLongStringShouldThrow() {
ee.expect(SomeException.class)
ee.expectMessage("some message");
myClass.myMethod(TEST_STRING2);
}
}
Upvotes: 3
Reputation: 95704
It's a credit to Mockito's developers that passing a mock seems to be easier than creating a real String. However, creating a real String is still the smartest option for your test.
You correctly noted that String is final
, so Mockito's can't use proxies or bytecode generation to silently create a subclass for it. You would instead be forced to use Powermock, which would actually rewrite the bytecode of your class-under-test using a custom classloader to use your mocked String.length() instead of the actual implementation. Either way, that's an awful lot of work to mimic a simple string, and may very well take more test time and memory than the alternative.
Even if String weren't final
, you'd still be writing a brittle test, because you'd (presumably) only be mocking a subset of String. In this hypothetical, you could write:
when(mockString.length()).thenReturn(2048);
but then, one day, your implementation turns to:
public boolean isStringValid(String input) {
// Handle Unicode surrogate pairs.
return input.codePointCount(0, input.length()) <= 1024; // NOT 2048
}
...and suddenly your test erroneously passes because Mockito sees an unmocked codePointCount
and returns its default int
return value, 0. This is one of the reasons that tests using incomplete mocks are less-resilient and less-valuable than tests using real objects.
But what about complete mocks? You could then mock codePointCount
, and so on. Taken to its logical conclusion, you could imagine a mock String object that is so advanced, it correctly returns a value for absolutely any call you give it. At that point you've reimplemented String inside-out, likely at the expense of readability, and spent many engineering hours reproducing one of the most-tested, most-obvious implementations in Java's history for very little reason. If anything, that seems pretty stupid to me.
A few guidelines:
Don't mock implementations you don't own. Mockito is still bound by final
markings in the implementations it mocks, for instance, so mocking other types is a bad habit to get into. When other teams' implementation details could break your tests, that's a really bad sign.
Don't mock objects when you can use a real, tested, authentic instance. There's rarely a good reason to mock in that case, and doing so will likely make your test unnecessarily-brittle.
In particular, don't mock data objects. They rely on getFoo
matching setFoo
, for instance, and may also rely on equals
and hashCode
working. In any case, well-designed data objects don't have external dependencies.
Mock interfaces instead of implementation classes when you can. That insulates you against implementation details that change the way your mocks work.
If you find yourself with a data object that makes service calls, or that uses resources that aren't good for testing, remember that you can refactor the classes to improve testability—such as extracting service methods into separate well-defined classes, or extracting interfaces and make custom reusable test doubles. Your tests should be first-class users of your classes, and it's your prerogative to change classes you control (or wrap classes you don't control) for the sake of testability. I've often found that this makes those classes easier to use in production.
Upvotes: 1