Search code examples
javaspring-bootunit-testingjunitmockito

How to write a unit test with a controller method that has protected code in it


I am trying to write a unit test for the following controller:

@RestController
@RequestMapping(value = ENDPOINT_URL)
public class MainController extends RestControllerBase {
    MainService mainService;

    public MainController(MainService mainService) {
        this.mainService = mainService;
    }

    @GetMapping
    public ResponseEntity<List<MainDataDto>> getDashboardData() {
        checkPermission();
        List<MainDataDto> result = mainService.getData();
        return ResponseEntity.ok().body(result);
    }
}

This is what I have so far in terms of a unit test:

class MainControllerTest {
    MainController mainController;

    @Test
    public void test_getDashboardData_shouldReturn200Response() {
        MainService mainService = Mockito.mock(MainService.class);

        mainController = new MainController(mainService);

        List<MainDataDto> mockData = List.of(new MainDataDto());
        when(mainService.getData()).thenReturn(mockData);

        Assertions.assertEquals(HttpStatus.OK, mainController.getDashboardData().getStatusCode());
}

When I run this unit test, I get the following exception:

java.lang.NullPointerException: Cannot invoke "org.springframework.security.core.Authentication.getPrincipal()" because the return value of "org.springframework.security.core.context.SecurityContext.getAuthentication()" is null

This exception is coming from checkPermission, which is a protected method in the RestControllerBase class. I cannot change the access modifier for this method.

I am new to writing unit tests, so I am trying to do this a small bit at a time so I can understand what I am doing. Because of the exception, I want to "figure out how to deal" with checkPermission and (with my limited understanding) I am thinking that I can/should somehow mock this method, or something along the lines of passing this part of my controller method over.

Being that this method is protected, what is the best way to change my unit test so I do not get the exception above anymore?


Solution

  • It looks like you are trying to test a spring-boot controller with Mockito, but you are trying to test the spring-security (or at least trying to get past it). You can accomplish this by adding the dependency to spring-security-test to your project.

    maven dependecy

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <version>6.3.1</version>
        <scope>test</scope>
    </dependency>
    

    gradle dependency

    testImplementation 'org.springframework.security:spring-security-test:6.3.1'
    

    Once the dependency is added to your project, you can annotate your test method with @WithMockUser. You will also need to annotate your test class with @SpringBootTest, but this won't require you to add any dependency injection, it just makes the @WithMockUser annotation available to your test.

    @SpringBootTest
    class MainControllerTest {
      MainController mainController;
    
      @Test
      @WithMockUser(username = "user1", password = "pwd", roles = "USER")
      void test_getDashboardData_shouldReturn200Response() {
        MainService mainService = Mockito.mock(MainService.class);
    
        mainController = new MainController(mainService);
    
        List<MainDataDto> mockData = List.of(new MainDataDto());
        when(mainService.getData()).thenReturn(mockData);
    
        Assertions.assertEquals(HttpStatus.OK, mainController.getDashboardData().getStatusCode());
      }
    }
    

    You can also use this with MockMvc if you decide to go that route, as that will give you a bit more control over testing the service layer without the need for an integration test. However, it will also work just fine with the test method as you have it written.