Как написать модульный тест с методом контроллера, в котором есть защищенный код

Я пытаюсь написать модульный тест для следующего контроллера:

@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);
    }
}

Вот что у меня есть на данный момент с точки зрения модульного теста:

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());
}

Когда я запускаю этот модульный тест, я получаю следующее исключение:

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

Это исключение исходит из checkPermission, который является защищенным методом в классе RestControllerBase. Я не могу изменить модификатор доступа для этого метода.

Я новичок в написании модульных тестов, поэтому стараюсь делать это понемногу, чтобы понять, что делаю. Из-за исключения я хочу «выяснить, как справиться» с checkPermission и (с моим ограниченным пониманием) я думаю, что могу/должен как-то высмеять этот метод или что-то вроде передачи этой части моего метода контроллера над.

Поскольку этот метод защищен, как лучше всего изменить мой модульный тест, чтобы я больше не получал приведенное выше исключение?

Защищенный метод означает, что вы все еще можете @Override его использовать. Но у SecurityContext есть общедоступные методы для установки аутентификации для текущего потока, поэтому вы можете просто установить ее в своем методе @BeforeEach и очистить в своем методе @AfterEach — или иметь блок try-finally в своем тестовом методе. См. связанный дубликат для первого подхода.

knittl 08.07.2024 21:00

Рассмотрите возможность использования макета MVC.

Valerij Dobler 08.07.2024 21:11
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
65
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В качестве краткосрочного решения модульный тест может шпионить за MainController, например:

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

что позволяет имитировать некоторое поведение тестируемого класса. Например:

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

Однако, поскольку часть класса, которую необходимо протестировать, больше не тестируется (поскольку ее поведение высмеивается), похоже, что класс следует перефакторизовать. Например, все, что делает checkPermission(), можно переместить в отдельный класс сервиса/компонента и внедрить в контроллер так же, как MainService. Это упростило бы настройку модульного теста с помощью макета нового класса, где тест подтверждал бы, что новый класс был вызван.

Ответ принят как подходящий

Похоже, вы пытаетесь протестировать контроллер весенней загрузки с помощью Mockito, но вы пытаетесь проверить пружинную безопасность (или, по крайней мере, пытаетесь обойти ее). Вы можете добиться этого, добавив в свой проект зависимость от Spring-security-test.

зависимость от maven

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

зависимость градиента

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

Как только зависимость будет добавлена ​​в ваш проект, вы можете аннотировать свой тестовый метод с помощью @WithMockUser. Вам также потребуется аннотировать свой тестовый класс с помощью @SpringBootTest, но это не потребует от вас добавления какой-либо инъекции зависимостей, а просто сделает аннотацию @WithMockUser доступной для вашего теста.

@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());
  }
}

Вы также можете использовать это с MockMvc, если решите пойти по этому пути, поскольку это даст вам немного больше контроля над тестированием уровня сервиса без необходимости проведения интеграционного теста. Однако он также будет отлично работать с написанным вами методом тестирования.

Другие вопросы по теме