Search code examples
mockitojunit5

Explain @Mock and @Spy in Java?


I recently started learning about unit testing and I came across the Mockito framework. I read about @Mock and @Spy when() etc..

@Mock
Service service;

@Test
void test(){
   when(service.runCode(Mock.anyString()).thenReturn("Working");
}

What my doubt in the above line is, will runCode(String a) be excuted OR once the method is invoked then what I had given in the thenReturn() part will returned by the mock?

I am trying to understand mock


Solution

  • Since you are new to unit testing, I'll write a simplified explanation of how Mocks work. Firstly, it is important to understand that dummies, fakes, mocks, spies are just different types of test objects that look like the actual object you use in production. They can be injected into the components you want to test to either verify the interaction and/or return some test data.

    To understand what mockito is doing under the hood, you can initially try to write your own mocks based on a public interface:

    public interface Service {
        String runCode(String arg);
    }
    

    For example, you can create a test implementation of this Service interface, that will always return the same hardcoded value, no-matter the argument passed in. This will be a Dummy:

    public class DummyService implements Service {
        @Override
        public String runCode(String arg) {
            return "dummy_response";
        }
    }
    

    As a result, you'll be able to use this in your test to avoid running the actual runCode method. Assuming you have a Controller class you want to test, that internally uses this Service, the test will look like this:

    @Test
    void test() {
        Controller c = new Controller(new DummyService());
        var response = c.doSomething("abc");
        // assert response ...
    }
    

    As you can see, dummy objects are rather dumb. Sometimes you might want to be able to return different Strings based on the input of the method. In this case, you can create your own Mock:

    public class MockedService implements Service {
        private Map<String, String> mockedData = new HashMap<>();
    
        public void whenRunCodeThenReturn(String arg, String mockedResponse) {
            mockedData.put(arg, mockedResponse);
        }
    
        @Override
        public String runCode(String arg) {
            return mockedData.get(arg);
        }
    }
    

    Now, you'll have to instantiate the MockedService and also specify some behavior, based on your test's needs:

    @Test
    void test() {
        MockedService mock = new MockedService();
        Controller c = new Controller(mock);
        mock.whenRunCodeThenReturn("abc", "mocked_response_for_abc");
        
        var response = c.doSomething("abc");
        // assert response ...
    }
    

    Of course, mockito is a powerful framework that offers more flexibility. It has features that allow you to define more complex conditions for the input parameters, it easily resets the mock after each test and it does not require the public interface to proxy the object.

    If you want to dive deeper into the framework, here is their documentation: https://site.mockito.org/