Как правильно протестировать 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
найти сущности. То, что я хотел бы знать, - как я могу издеваться над этим в тесте. Я добился определенного успеха, выполнив следующие действия:
- Создайте пользовательский конвертер / форматтер, который я могу издеваться
- Создайте свой собственный MockMvc с указанным выше конвертером
- сбросить макет и изменить поведение при каждом тесте.
Проблема в том, что установочный код сложен, и, следовательно, его сложно отлаживать и объяснять (моя команда на 99% состоит из младших ребят из рельс или универа, поэтому мы должны упростить задачу). Мне было интересно, есть ли способ ввести желаемое MyEntity
экземпляры из моего модульного теста, в то время как продолжать тестирование с использованием @Autowired
MockMvc
,
В настоящее время я пытаюсь понять, смогу ли я сделать инъекцию 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 позволяла это.