Search code examples
javamockitofluent

How, briefly, did Mockito implement its fluent syntax for stubbing?


I'm in a situation now where I need to implement something similar to Mockito's fluent syntax:

when( mock.arbitraryMethod( arg1, matcher2 ) ).thenReturn( foo );

I spent about 10 minutes looking over the source at github without seeing the basic pattern that achieves what amounts to quoting the arbitrary method. Somehow the mock object has to record what method was called, and the arguments passed to it, in some buffer which when() can subsequently obtain (it can't "see" the mock or the method, which will be eagerly evaluated beforehand); and the problem compounds when you pass a matcher to that method instead of a regular argument.

Can someone sum up the trick?


Solution

  • A little code helps illustrate what is happening, with some surprising results. The way you are expected to use Mockito (and the way you should use Mockito) normally look something like this:

        @Test
        public void whenClauseExplanation_v1() {
            MyImpl myMock = mock(MyImpl.class);
    
            when(myMock.getStringObj()).thenReturn("What In The World?");
    
            assertThat(myMock.getStringObj()).isEqualToIgnoringCase("WHAT IN THE WORLD?");
        }
    

    However, you can take apart the way it is actually invoked and see some surprising results:

        @Test
        public void whenClauseExplanation_v2() {
            // 1. The argument to when() is a method call on the mock object, the method being stubbed.
            // 2. Argument gets evaluated before when() is invoked
            // 3. Eval of method call on mock records the invocation and arguments in ThreadLocal storage
            // 4. "when()" is invoked, pops the most recent recorded invocation
            // 5. ".thenReturn()" stubs the method to return a specific value when a matching invocation is encountered
    
            // To demonstrate this:
    
            MyImpl myMock = mock(MyImpl.class);
    
            myMock.getStringObj(); // primes the pump
            when(null).thenReturn("What In The World?");  // Huh? passing null?
    
            // The argument itself doesn't really matter!  But ordinary usage ensures the invocation is recorded before "when()" is evaluated.
            assertThat(myMock.getStringObj()).isEqualToIgnoringCase("WHAT IN THE WORLD?");
        }
    

    As you can see, the argument to when() doesn't even matter, except to provide type-safety for the expected return type that may be provided in the argument to thenReturn()