-1

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?

2
  • The method being protected means you can still @Override it. But SecurityContext has public methods to set the authentication for the current thread, so you should be able to simply set it in your @BeforeEach method and clear it in your @AfterEach method – or have a try-finally block in your test method. See linked duplicate for the first approach.
    – knittl
    Commented Jul 8 at 19:00
  • Consider using mockMVC. Commented Jul 8 at 19:11

2 Answers 2

1

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.

0
1

As a short-term solution, the unit test can spy on MainController, for example:

mainController = Mockito.spy(new MainController(mainService));

which allows to mock some behavior of the class under test. For example:

    doNothing().when(mainController).checkPermission();

However, since part of the class that needs to be tested is no longer tested (since its behavior is mocked) it sounds like the class should be re-factored. For example, whatever checkPermission() is doing could be moved to its own service/component class, and injected into the controller just like MainService. That would simplify the unit test setup with a mock of the new class, where a test verified the new class was invoked.

Not the answer you're looking for? Browse other questions tagged or ask your own question.