Search code examples
javaspringspring-bootjunit5

NullPointerException in Junit 5 @MockBean


I wrote a JUnit 5 test for my service in my Spring Boot application. I used @MockBean to mock PasswordEncoder and other beans but I obtain a NullPointerException.

I always obtain a NullPointerException during the when call : when(compteRepository.getByLogin(anyString())).thenReturn(Optional.of(acc));

Service

package com.compte.application.impl;

import com.compte.application.CompteService;
import com.compte.domain.exceptions.EntityNotFoundExcpetion;
import com.compte.domain.model.Compte;
import com.compte.domain.model.CompteUpdatedData;
import com.compte.domain.repository.CompteRepository;
import com.compte.domain.utils.CompteUtil;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.time.LocalDate;
import java.util.Optional;

/**
 * @author mbint
 */
@AllArgsConstructor
public class CompteServiceImpl implements CompteService{

    private final static Logger LOGGER = LoggerFactory.getLogger(CompteService.class);

    private CompteRepository CompteRepository;
    private PasswordEncoder passwordEncoder;

    @Override
    public Optional<Compte> getByLogin(String login) {
        return CompteRepository.getByLogin(login);
    }

    @Override
    public void update(final Long id, CompteUpdatedData updatedData) {
        Optional<Compte> optional = CompteRepository.getById(id);
        if(optional.isPresent()) {
            Compte Compte = optional.get();
            Compte.setFirstName(updatedData.getFirstName());
            Compte.setLastName(updatedData.getLastName());
            CompteRepository.save(Compte);
        } else {
            throw new EntityNotFoundExcpetion("Compte: " + id + " not found !!");
        }
    }
}

Junit Test

package com.compte.application;

import com.compte.application.impl.CompteServiceImpl;
import com.compte.domain.model.Compte;
import com.compte.domain.model.CompteUpdatedData;
import com.compte.domain.repository.compteRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.mockito.Mockito.*;


/**
 * @author mbint
 */
public class CompteServiceImplTest {

    private final static String PASSWORD = "Passw00rd";

    @MockBean
    private compteRepository compteRepository;
    @MockBean
    private PasswordEncoder passwordEncoder;
    private CompteService CompteService = new CompteServiceImpl(compteRepository, passwordEncoder);

    @DisplayName(("Should return existing user"))
    @Test
    private void given_login_then_return_existing_user() {
        Compte acc = Compte.builder().id(1L)
                .firstName("Luc")
                .lastName("JOJO")
                .login("xxx@gmail.com")
                .password("xxxxxxxxxxxxxxx")
                .build();
        when(compteRepository.getByLogin(anyString())).thenReturn(Optional.of(acc));

        Optional<Compte> optional = CompteService.getByLogin("xxx@gmail.com");
        Compte Compte = optional.get();

        Assertions.assertSame(1L, acc.getId());
        Assertions.assertSame("xxx@gmail.com", Compte.getLogin());
    }

    @DisplayName("Should update existing user")
    @Test
    public void given_edited_Compte_then_update_user() {
        Compte acc = Compte.builder().id(1L)
                .firstName("Luc")
                .lastName("JOJO")
                .email("xxx@gmail.com")
                .password("xxxxxxxxxxxxxxx")
                .build();
        when(compteRepository.getById(anyLong())).thenReturn(Optional.of(acc));

        CompteUpdatedData updatedData = CompteUpdatedData.builder()
                .firstName("Moos")
                .lastName("Man")
                .build();
        CompteService.update(1L, updatedData);
        Assertions.assertSame("Moos", acc.getFirstName());
    }

    private List<Compte> getComptes() {
        List<Compte> Comptes = new ArrayList<>();

        Compte acc1 = Compte.builder()
                .id(1L)
                .firstName("Luc")
                .lastName("JOJO")
                .email("xxx@gmail.com")
                .login("xxx@gmail.com")
                .build();
        Comptes.add(acc1);

        Compte acc2= Compte.builder()
                .id(2L)
                .firstName("Jean")
                .lastName("KELLY")
                .email("jean.kelly@gmail.com")
                .login("jean.kelly@gmail.com")
                .build();
        Comptes.add(acc2);

        Compte acc3= Compte.builder()
                .id(3L)
                .firstName("Marc")
                .lastName("BARBY")
                .email("marc.barby@gmail.com")
                .login("marc.barby@gmail.com")
                .build();
        Comptes.add(acc3);

        return Comptes;
    }

}

Spring boot application

package com.compte;

import com.compte.application.CompteService;
import com.compte.application.impl.CompteServiceImpl;
import com.compte.domain.repository.CompteRepository;
import com.compte.infrastructure.repository.database.CompteDBRepositiry;
import com.ombsc.bargo.common.config.SwaggerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.hateoas.client.LinkDiscoverer;
import org.springframework.hateoas.client.LinkDiscoverers;
import org.springframework.hateoas.mediatype.collectionjson.CollectionJsonLinkDiscoverer;
import org.springframework.plugin.core.SimplePluginRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.ArrayList;
import java.util.List;

@ComponentScan({"com.compte.interfaces.interfaces"})
@SpringBootApplication
@Import({SwaggerConfig.class})
public class CompteApplication {

    public static void main(String[] args) {
        SpringApplication.run(CompteApplication.class, args);
    }

    @Bean
    public CompteRepository getRepository() {
        return new CompteDBRepositiry();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Bean
    public CompteService CompteService(CompteRepository repository, PasswordEncoder passwordEncoder) {
        return new CompteServiceImpl(repository, passwordEncoder);
    }

    @Bean
    public LinkDiscoverers discovers() {
        List<LinkDiscoverer> plugins = new ArrayList<>();
        plugins.add(new CollectionJsonLinkDiscoverer());
        return new LinkDiscoverers(SimplePluginRegistry.create(plugins));
    }
}

Solution

  • The mocks need to be initialized before they can be used. There are several options to do this.

    The first option would be to use @SpringExtension which will initialize the mocks annotated with @MockBean:

    @ExtendWith(SpringExtension.class)
    public class CompteServiceImplTest {
        @Autowired
        private CompteService CompteService;
    
        @MockBean
        private compteRepository compteRepository;
    
        // ...
    }
    

    This will make sure that the repository bean is mocked before the service bean is autowired.

    However, since you are writing a unit test for the service, you don't need the Spring extension at all. The second option is to use @Mock instead of @MockBean, and call @InjectMocks in conjunction with the MockitoExtension for constructing the service under test:

    @ExtendWith(MockitoExtension.class)
    public class CompteServiceImplTest {
        @InjectMocks
        private CompteService CompteService;
        @Mock
        private compteRepository compteRepository;
    
        // ...
    }
    

    Alternatively, you could just call MockitoAnnotations.initMocks(), which will initialize the mocks annotated with @Mock, and use constructor injection for your service:

    public class CompteServiceImplTest {
        private CompteService CompteService;
    
        @Mock
        private compteRepository compteRepository;
    
        @BeforeEach
        void setUp() {
            MockitoAnnotations.initMocks(this);
            CompteService = new CompteServiceImpl(compteRepository, ...);
        }
    
        // ...
    }
    

    Finally, you could do it all without annotations just by calling Mockito.mock() directly:

    public class CompteServiceImplTest {
        private compteRepository compteRepository;
    
        @BeforeEach
        void setUp() {
            compteRepository = Mockito.mock();
            CompteService = new CompteServiceImpl(compteRepository, ...);
        }
    
        // ...
    }
    

    EDIT:

    Since Mockito 3, the MockitoAnnotations.initMocks() has been deprecated in favour of MockitoAnnotations.openMocks(). This method returns an instance of AutoCloseable which can be used to close the resource after the test.

    public class CompteServiceImplTest {
        private CompteService CompteService;
    
        @Mock
        private compteRepository compteRepository;
    
        private AutoCloseable closeable;
    
        @BeforeEach
        void setUp() {
            closeable = MockitoAnnotations.openMocks(this);
            CompteService = new CompteServiceImpl(compteRepository, ...);
        }
    
        @AfterEach
        void tearDown() {
            closeable.close();
        }
    
        // ...
    }