javamockitoguicejukito

First stub is called when adding an additional stub


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.


Solution

  • 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.