3

I have the following object which I want to test:

   public class MyObject {

    @Inject
    Downloader downloader;

    public List<String> readFiles(String[] fileNames) {
        List<String> files = new LinkedList<>();
        for (String fileName : fileNames) {
            try {
                files.add(downloader.download(fileName));
            } catch (IOException e) {
                files.add("NA");
            }
        }
        return files;
    }
}

This is my test:

@UseModules(mockTest.MyTestModule.class)
@RunWith(JukitoRunner.class)
public class mockTest {

    @Inject Downloader downloader;
    @Inject MyObject myObject;

    private final String[] FILE_NAMES = new String[] {"fail", "fail", "testFile"};
    private final List<String> EXPECTED_FILES = Arrays.asList("NA", "NA", "mockContent");

    @Test
    public void testException() throws IOException {
        when(downloader.download(anyString()))
                .thenThrow(new IOException());

        when(downloader.download("testFile"))
                .thenReturn("mockContent");

        assertThat(myObject.readFiles(FILE_NAMES))
                .isEqualTo(EXPECTED_FILES);
    }

    public static final class MyTestModule extends TestModule {
        @Override
        protected void configureTest() {
            bindMock(Downloader.class).in(TestSingleton.class);
        }
    }
}

I am overwriting the anyString() matcher for a specific argument. I am stubbing the download() method so that it returns a value for a specific argument and otherwise throws an IOException which gets handled by MyObject.readFiles.

The weird thing here is that the second stub (downloader.download("testFile")) throws the IOException set in the first stub (downloader.download(anyString())). I have validated that by throwing a different exception in my first stub.

Can someone explain me why the exception is thrown when adding an additional stub? I thought that creating a stub does not call the method/other stubs.

fwind
  • 1,274
  • 4
  • 15
  • 32

3 Answers3

2

The problem is that when you write

when(downloader.download("testFile")).thenReturn("mockContent");

the first thing to be called is downloader.download, which you've already stubbed to throw an exception.

The solution is to use the slightly more versatile stubbing syntax that Mockito provides. This syntax has the advantage that it doesn't call the actual method when stubbing.

doThrow(IOException.class).when(downloader).download(anyString());
doReturn("mock content").when(downloader).download("test file");

I have listed other advantages of this second syntax, in my answer here

Community
  • 1
  • 1
Dawood ibn Kareem
  • 77,785
  • 15
  • 98
  • 110
1

Your 2nd mock statement is getting overriden by the first mock statement (because both mock statements are passing a String argument). If you want to cover try as well as catch back through your mock test then write 2 different test cases.

kk.
  • 3,747
  • 12
  • 36
  • 67
1

I thought that creating a stub does not call the method/other stubs.

This assumption is wrong, because stubbing is calling the mocks methods. Your test methods are still plain java!

Since stubbing for anyString will overwrite stubbing for any specific string you will either have to write two tests or stub for two specific arguments:

when(downloader.download("fail")).thenThrow(new IOException());

when(downloader.download("testFile")).thenReturn("mockContent");

Mockito is a very sophisticated piece of code that tries its best so that you can write

when(downloader.download(anyString())).thenThrow(new IOException());

which means “when the downloaders mock download method is called with anyString argument thenThrow an IOException” (i.e. it can be read from left to right).

However, since the code is still plain java, the call sequence actually is:

String s1 = anyString(); // 1
String s2 = downloader.download(s1); // 2
when(s2).thenThrow(new IOException()); // 3

Behind the scenes, Mockito needs to do this:

  1. register an ArgumentMatcher for any String argument
  2. register a method call download on the downloader mock where the argument is defined by the previously registered ArgumentMatcher
  3. register an action for the previously registered method call on a mock

If you now call

 ... downloader.download("testFile") ...

the downloader mock checks whether there is an action register for "testFile" (there is, since there is already an action for any String) and accordingly throws the IOException.

Thomas Kläger
  • 17,754
  • 3
  • 23
  • 34