I am writing a unit tests to test REST API endpoints. I am using MockMvc to handle API testing and @InjectMocks to load endpoint and @Mock to mock the service layer. Below are snippets.
BookController
@RestController
@RequestMapping( path="api/v1/book")
public class BookController {
private final BookService bookService;
@Autowired
public BookController (BookService bookService){
this.bookService = bookService;
}
@GetMapping
public List<Book> getBooks() {
return bookService.getAllBook();
}
}
BookService
@Service
public class BookService {
private final BookRepository bookRepository;
@Autowired
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public List<Book> getAllBook() {
return bookRepository.findAll();
}
}
BookRepository
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findAll();
}
TestClass
@ExtendWith(MockitoExtension.class)
public class BookControllerTest {
private MockMvc mockMvc;
@InjectMocks
private BookController bookController;
@Mock
private BookService bookService;
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(bookController).build();
}
@Test
public void getAllRecords_success() throws Exception {
List<Book> records = Arrays.asList(record1, record2, record3);
Mockito.when(bookService.getAllBook()).thenReturn(records);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders
.get("/api/v1/book")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
}
}
When I run the test I got UnnecessaryStubbingException. The MvcResult result variable has empty massive rather than a massive of three books (records). So, I believe the cause is that I probably do not understand very well how to use Mockito. Could you please point me in the right direction?
SpringBoot version is 3.2.1.
Thanks a lot!
=============================
Update#1
I have found that if I change Controller like this:
@RestController
@RequestMapping( path="api/v1/book")
public class BookController {
// private final BookService bookService;
BookService bookService;
// @Autowired
// public BookController (BookService bookService){
// this.bookService = bookService;
// }
@GetMapping
public List<Book> getBooks(){
return bookService.getAllBooks();
}
}
then mocking works and MvcResult result variable has a massive of three books (records). Could any one explain how to work it around since with the updated controller the spring app returns 500 code on sending via postman a get request on '/api/v1/book' endpoint. There is in console:
NullPointerException: Cannot invoke "mypackage.BookService.getAllBooks()" because "this.bookService" is null
You are initializing your mocks twice:
MockitoExtension
(which internally calls initMocks
/ openMocks
)initMocks
Double initialization fails if you inject a mock to a final field
Check this example:
interface Foo {
}
class Bar {
final Foo foo;
Bar(Foo foo) {
this.foo = foo;
}
}
@ExtendWith(MockitoExtension.class)
public class InitMocksTest {
@InjectMocks Bar bar;
@Mock Foo foo;
@BeforeEach
void setup() {
System.out.println("bar: " + defaultToString(bar));
System.out.println("foo: " + defaultToString(foo));
System.out.println("bar.foo: " + defaultToString(bar.foo));
System.out.println();
MockitoAnnotations.openMocks(this);
System.out.println("bar: " + defaultToString(bar));
System.out.println("foo: " + defaultToString(foo));
System.out.println("bar.foo: " + defaultToString(bar.foo));
}
public static String defaultToString(Object o) {
return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode());
}
@Test
void dummy() {
}
}
Output:
bar: com.so.examples.mockito.Bar@5d332969
foo: com.so.examples.mockito.Foo$MockitoMock$v4JhQAjO@6ca320ab
bar.foo: com.so.examples.mockito.Foo$MockitoMock$v4JhQAjO@6ca320ab
bar: com.so.examples.mockito.Bar@5d332969
foo: com.so.examples.mockito.Foo$MockitoMock$v4JhQAjO@1e34c607
bar.foo: com.so.examples.mockito.Foo$MockitoMock$v4JhQAjO@6ca320ab
Observe that after second initialization: