4

Given the following @Component class:

@Component
public class MovieFinderImpl implements MovieFinder {

    @Autowired
    private Movie movie;

    @Override
    public List<Movie> findAll() {      
        List<Movie> movies = new ArrayList<>();
        movies.add(movie);
        return movies;
    }

}

I am trying to learn how to unit test this example component without doing an integration test (so no @RunWith(SpringRunner.class) and @SpringBootTest annotations on the test class).

When my test class looks like this:

public class MovieFinderImplTest {

    @InjectMocks
    private MovieFinderImpl movieFinderImpl;

    @Mock
    public Movie movieMock;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        movieMock.setTitle("test");
        movieMock.setDirector("directorTest");
    }

    @Test
    public void testFindAll() {         
        List<Movie> movies = movieFinderImpl.findAll();
        Assert.assertNotNull(movies.get(0));

        String expectedTitle = "test";
        String actualTitle = movies.get(0).getTitle();
        Assert.assertTrue(String.format("The expected name is %s, but the actual name is %s", expectedTitle, actualTitle), expectedTitle.equals(actualTitle));

        String expectedDirector = "testDirector";
        String actualDirector = movies.get(0).getDirector();
        Assert.assertTrue(String.format("The expected name is %s, but the actual name is %s", expectedDirector, actualDirector), expectedDirector.equals(actualDirector));
    }
}

... the mock is not null, but the mock class variables are and thus:

java.lang.AssertionError: The expected name is test, but the actual name is null

I have browsed through http://www.vogella.com/tutorials/Mockito/article.html , but was unable to find an example of how to set a class variable on a mock.

How do I properly mock the movie object? More in general is this the proper way to test this MovieFinderImp class? My inspiration of just component testing was this blog https://spring.io/blog/2016/04/15/testing-improvements-in-spring-boot-1-4

(ps: I wonder if I should actually test movie.get() method in this test class...perhaps my test design is just wrong).

Sander_M
  • 1,109
  • 2
  • 18
  • 36
  • 5
    As a general rule, you should only mock classes that have some functionality. A "value object" - that is, a class that's just a bunch of data with getters and setters - is not something that you'd bother mocking. Just use a real `Movie` instead. – Dawood ibn Kareem Aug 16 '16 at 10:58
  • Yes, when writing the question it already popped in my mind that mocking a very simple object is probably not worth it, but as I am not very experienced with the Mockito library and mocking in general I thought I'd ask for help. Thanks for the suggestion – Sander_M Aug 16 '16 at 11:59

3 Answers3

8

There is a problem with the way you are mocking in a @Before method. Instead of

movieMock.setTitle("test");
movieMock.setDirector("directorTest");

Do it like that

Mockito.when(movieMock.getTitle()).thenReturn("test");
Mockito.when(movieMock.getDirector()).thenReturn("directorTest");
marians27
  • 311
  • 1
  • 7
5

Mocked objects are not real objects and have no properties only mocked behaviour that you need to add. Using the setter won't do anything to them. The getter will return null unless you specify the spected behaviour. That is can be done using the when... thenReturn structure:

when(movieMock.getTitle()).thenReturn("movie title");

Maybe there is no reason to mock Movie. You can use the @Spy annotation and this way it will be a real object with real properties and and the same time you can override some methods.

Mocks are really usefull when building the full object is hard and have lots of depenencies or complex behaviour but may be an overkill if Movie is a bean. For example:

public class MovieFinderImplTest {

    @InjectMocks
    private MovieFinderImpl movieFinderImpl;

    @Spy /* <- See the change from mock */
    public Movie movie = new Movie();

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        /* modified like a normal object */
        movie.setTitle("test");
        movie.setDirector("directorTest");
    }

See also the difference between mock and spy. There are long discussions in internet about what needs to be mocked and what not. If you still prefer to mock moview then marians27 and VinayVeluri answer is the rigth answer for you.

Community
  • 1
  • 1
borjab
  • 11,149
  • 6
  • 71
  • 98
  • I was about to answer the `@Spy` thing as well, but then I thought that it's not really a spy either and using that annotation doesn't really make it much better. The issue is within the code itself, autowiring a class like `Movie` doesn't really look like the way to go here. – g00glen00b Aug 16 '16 at 12:21
  • 1
    Thanks for the detailed explanation. In the actual code I think I do really need to mock a certain more complex object, instead of using an actual object. Hence I choose to accept the answer of marians27. The autowiring of the movie object here is just for example purpose. – Sander_M Aug 16 '16 at 12:47
2

Here is what you have to do;

import static org.mockito.Mockito.when;

when(movieMock.getTitle()).thenReturn("movie title");
when(movieMock.getDirector()).thenReturn("movie director");

@InjectMocks helps only to inject not null objects and doesnt deal with values. While with values, we have to explicitly set the values we need to test.

If you use, MockitoJunitRunner there is no need to call MockitoAnnotations.initMocks(this); which runner takes care of.

Vinay Veluri
  • 6,671
  • 5
  • 32
  • 56
  • I have annotated my class with `@RunWith(MockitoJUnitRunner.class)`. Thanks! Is this the official / preferred way to use Mockito over using `MockitoAnnotations.initMocks(this);` ? – Sander_M Aug 16 '16 at 12:54
  • Runner has many advantages, Please have a look at http://stackoverflow.com/questions/15494926/initialising-mock-objects-mockito for better understanding – Vinay Veluri Aug 17 '16 at 10:39