D.Tomov
D.Tomov

Reputation: 1168

How to run a unit test using the SpringBoot 2

I have the following test:

@SpringBootTest
@ExtendWith(SpringExtension.class)
class BookServiceImplTest {
    @MockBean
    private BookRepository bookRepository;
    @MockBean
    private LibraryService libraryService;

    @Autowired
    private BookServiceImpl bookService;

    @Test
    void create() {
        BookRequestDTO bookRequestDTO = new BookRequestDTO();
        Library library = new Library();
        Book expectedBook = new Book();
        when(libraryService.getById(bookRequestDTO.getLibraryId()))
                .thenReturn(library);
        when(bookRepository.save(any(Book.class)))
                .thenReturn(expectedBook);

        Book actualBook = bookService.create(bookRequestDTO);

        assertEquals(expectedBook, actualBook);
    }
}

It is okay, and it runs, but I was wondering is there a way to run this as a Unit test instead of a Integration tests and still use @MockBean @Autowired. Or am I missing some point?

I tried leaving only @ExtendWith(SpringExtension.class), but I get an Exception about BookServiceImpl bean not found.

I know how to do it using MockitoExtension and @Mock, @InjectMocks, but I was wondering if there is a more SpringBoot way of doing it?

Upvotes: 1

Views: 820

Answers (2)

stjernaluiht
stjernaluiht

Reputation: 750

You can make it unit test in four steps:

  • Remove @SpringBootTest annotation, as it is spinning up the entire Spring context ―not useful for unit tests where all collaborators are mocked
  • Remove @Autowired annotation from BookServiceImpl declaration and add a @BeforeEach setup method where you initialize bookService passing bookRepository and libraryService as parameters
  • Use MockitoExtension instead of SpringExtension in the ExtendWith annotation. Here I'm assuming you are able to use a library like Mockito for mocking your collaborators
  • Use Mockito's @Mock instead of @MockBean, as we are manually initializing bookService so there is no need to deal with Spring beans

To add a bit more on your first question: @Mockbean and @Autowired are annotations that make sense for integration tests as they handle the mocking and injection of beans. Unit tests should consider this class in isolation, mocking interactions with other classes, so there is no need to spin up the application context and set up beans.

Upvotes: 2

HMD
HMD

Reputation: 460

  1. Remove @SpringBootTest, this will load whole context and slow down your tests. The role of @MockBean is to create a mock from the specified bean and add it to the context. Since there's no context running, There's not point in using @MockBean

  2. Annotate your unit test class with @RunWith(SpringRunner.class)

  3. For injecting dependencies, this is a good practice to create a configuration file and mock beans explicitly and create the target bean using them. Assuming you're using constructor-based injection:

    @Profile("test")
    @Configuration
    public class BookServiceTestConfiguration {
    
    
        @Bean
        public BookRepository bookRepository() {
            return Mockito.mock(BookRepository.class);
        }
    
        @Bean
        public LibraryService libraryService() {
            return Mockito.mock(LibraryService.class);
        }
    
        @Bean
        public BookService bookService() {
           BookServiceImpl bookService = new BookServiceImpl(
                    bookRepository(), libraryService()
            );
    
           return userGroupService;
        }
    }
    

Then write your test class as:

    @ActiveProfiles("test")
    @Import(BookServiceTestConfiguration .class)
    @RunWith(SpringRunner.class)
    public class BookServiceUnitTest {

        @Autowired
        private BookService bookService;

        // write unit tests
    }
  1. for further information, read this article

Upvotes: 0

Related Questions