3

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?

Gustavo Barbosa
  • 1,340
  • 1
  • 17
  • 27
Sachin
  • 155
  • 1
  • 2
  • 8
  • I don't understand the problem, could you explain a little more or post some code? If you are trying to throw and exception and check it, you should add the parameter to the @Test annotation (expected = YourException.class) – Luke SpringWalker Oct 21 '14 at 03:16
  • Why passing a string is stupid? I've seen too many abuse of Mock around. – Alex Suo Oct 21 '14 at 03:34
  • For the record, there is a similar [How to mock a String using mockito?](http://stackoverflow.com/q/1079239/1426891) question already on StackOverflow. I don't think it's a duplicate, though—this question regards mocking strategy instead of library capabilities. – Jeff Bowman Oct 21 '14 at 03:49
  • user2267921: Yes, I am trying to test an exception via @test annotation but my test method throws exception only if the input string is more than 2048 characters – Sachin Oct 21 '14 at 05:10
  • Alex: What if later I wanted to throw an exception when my string is more than say 100,000 characters. I don't want to create a big string constant in my test file. I want s string mock that can return like a huge defined number when calling length() method. – Sachin Oct 21 '14 at 05:12

2 Answers2

3

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

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.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • I agree with all the points you mentioned. What if the character limit is changed from 2048 to lets say 20K or may be even more. In that case do you still recommend creating a real string? – Sachin Oct 21 '14 at 05:25
  • 1
    **Yes, without a second thought.** Use a real String for any case that the system-under-test might find a real string in the wild—2k, 20k, 2M. Much larger than that and String may not be the right data type, in testing or production, because Strings are big contiguous immutable blocks of memory; I'd double-check my design and consider switching to CharSequence (a small and mockable interface) and then using some kind of [Rope](http://en.wikipedia.org/wiki/Rope_(data_structure)) in production. NB: CharSequence exposes `toString`, anyway. :) – Jeff Bowman Oct 21 '14 at 05:40