Home > OS >  unable to mock a constructor with Mockito
unable to mock a constructor with Mockito

Time:10-23

Using Mockito version 4.8.0

The controller method I need to test

 @GetMapping(value = "getStringBuiltByComplexProcess")
 public String getStringBuiltByComplexProcess(@RequestParam String firstName, @RequestParam String lastName ) {
  Author a = new Author();
  return a.methodWhichMakesNetworkAndDatabaseCalls(firstName, lastName);
 }

here is the test method

 @Test
 public void testGetStringBuiltByComplexProcess01() {
  final String firstName = "firstName";
  final String lastName = "lastName";
  try (MockedConstruction<Author> mock = mockConstruction(Author.class)) {
   Author authorMock = new Author();
   when(authorMock.methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName))).thenReturn("when worked");
   assertEquals("when worked", ut.getStringBuiltByComplexProcess(firstName, lastName),  "Strings should match");
   verify(authorMock).methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName));
  }
 }

fails with a message of

org.opentest4j.AssertionFailedError: strings should match ==> expected: <when worked> but was: <null>

In this simplified example the controller method has more code but the core of what is not working is mocking the object which the controller method constructs.

CodePudding user response:

The object you create on line

Author authorMock = new Author();

is different than the one created in the getBooksByAuthor() function. A debugger should show you that.

You can use mock.constructed().get(0) to get the object created in getBooksByAuthor(), but by the time you can do this, getBooksByAuthor() has already finished and you can't do much with that mock.

It's not exactly clear what your objective is. I guess you want to check that the Author object is created in a certain way, and the lines involving getFullName() aren't part of the actual code, just something you added to experiment, because they don't do anything.

If you want to verity that the object passed to dataAccessService satisfies some conditions, what you need is an ArgumentCaptor. Something like

ArgumentCaptor<Author> authorCaptor = ArgumentCaptor.forClass(Author.class);
when(dataAccessServiceMock.getBooks(authorCaptor.capture())).thenReturn(books);

List<Book> result = ut.getBooksByAuthor(firstName, lastName);

Author author = authorCaptor.value();
assertEquals(firstName, author.getFirstName());

CodePudding user response:

If you use MockInitializer for stubbing , it should solve your problem :

try (MockedConstruction<Author> mocked = mockConstruction(Author.class, (mock, context) -> {
        when(authorMock.methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName))).thenReturn("when worked");
        })) {

        assertEquals("when worked", ut.getStringBuiltByComplexProcess(firstName, lastName),  "Strings should match");
        verify(authorMock).methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName));

        }
}

But a much better way to test the controller is to use MockMvc. It allows you to test for a given HTTP request , do you configure spring-mvc properly such that it can do the following things correctly :

  • if the HTTP request can be parsed properly to execute the expected controller method with the expected paramater
  • if it can deserialize the object returned from the controller method into a expected JSON structure.
  • it allows to configure the current user who make the HTTP call and verify if he has enough permission to call this API and if not, will it return the expected error response
  • etc.

All of these things cannot be tested by your fragile mocking constructor approach.

For more details about MockMvc, refer to this guide.

  • Related