Как правильно протестировать Spring Controllers, которые получают параметры с DomainClassConverter?

Я большой на чистых хорошо изолированных модульных тестах. Но я натыкаюсь на "чистую" часть здесь для тестирования контроллера, который использует DomainClassConverter функция, чтобы получить сущности в качестве параметров для своих сопоставленных методов.

@Entity
class MyEntity {
    @Id
    private Integer id;
    // rest of properties goes here.
}

Контроллер определяется так

@RequestMapping("/api/v1/myentities")
class MyEntitiesController {
    @Autowired
    private DoSomethingService aService;

    @PostMapping("/{id}")
    public ResponseEntity<MyEntity> update(@PathVariable("id")Optional<MyEntity> myEntity) {
        // do what is needed here
    }
}

Так из DomainClassConverter небольшая документация, я знаю, что он использует CrudRepository#findById найти сущности. То, что я хотел бы знать, - как я могу издеваться над этим в тесте. Я добился определенного успеха, выполнив следующие действия:

  1. Создайте пользовательский конвертер / форматтер, который я могу издеваться
  2. Создайте свой собственный MockMvc с указанным выше конвертером
  3. сбросить макет и изменить поведение при каждом тесте.

Проблема в том, что установочный код сложен, и, следовательно, его сложно отлаживать и объяснять (моя команда на 99% состоит из младших ребят из рельс или универа, поэтому мы должны упростить задачу). Мне было интересно, есть ли способ ввести желаемое MyEntity экземпляры из моего модульного теста, в то время как продолжать тестирование с использованием @AutowiredMockMvc,

В настоящее время я пытаюсь понять, смогу ли я сделать инъекцию CrudRepository за MyEntity но безуспешно Я не работал в Spring/Java в течение нескольких лет (4), поэтому мои знания о доступных инструментах могут не соответствовать современным требованиям.

0 ответов

Итак, из небольшой документации DomainClassConverter я знаю, что он использует CrudRepository#findById для поиска объектов. Что я хотел бы знать, так это как я могу чисто поиздеваться над этим в тесте.

Вам нужно будет издеваться над двумя методами, которые вызываются до CrudRepository#findByIdчтобы вернуть желаемый объект. В приведенном ниже примере используетсяRestAssuredMockMvc, но вы можете сделать то же самое с MockMvc, если вставите WebApplicationContext также.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SomeApplication.class)
public class SomeControllerTest {

    @Autowired
    private WebApplicationContext context;

    @MockBean(name = "mvcConversionService")
    private WebConversionService webConversionService;

    @Before
    public void setup() {
        RestAssuredMockMvc.webAppContextSetup(context);

        SomeEntity someEntity = new SomeEntity();

        when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
                .thenReturn(true);

        when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
                .thenReturn(someEntity);
    }
}

В какой-то момент Spring Boot выполнит WebConversionService::convert, который позже вызовет DomainClassConverter::convert а потом что-то вроде invoker.invokeFindById, который будет использовать репозиторий сущностей для поиска сущности.

Так зачем насмехаться WebConversionService вместо того DomainClassConverter? Потому какDomainClassConverter создается при запуске приложения без инъекции:

DomainClassConverter<FormattingConversionService> converter =
        new DomainClassConverter<>(conversionService);

Между тем, WebConversionService это bean-компонент, который позволит нам издеваться над ним:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
}

Важно назвать фиктивный компонент как mvcConversionService, иначе он не заменит исходный компонент.

Что касается заглушек, вам нужно будет смоделировать 2 метода. Сначала вы должны сказать, что ваш макет может конвертировать что угодно:

when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
        .thenReturn(true);

И затем основной метод, который будет соответствовать желаемому идентификатору объекта, указанному в пути URL:

when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
        .thenReturn(someEntity);

Все идет нормально. Но не лучше ли было бы сопоставить и тип назначения? Что-то типаeq(TypeDescriptor.valueOf(SomeEntity.class))? Было бы, но это создает новый экземпляр TypeDescriptor, который не будет соответствовать, когда эта заглушка вызывается во время преобразования домена.

Это было самое чистое решение, которое я применил, но я знаю, что было бы намного лучше, если бы Spring позволяла это.

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