#java #spring-boot #kotlin #mocking #mockito
#java #весенняя загрузка #kotlin #издевательство #mockito
Вопрос:
Я пытаюсь использовать mockito версии 3.7.0 для тестирования моих контроллеров Java 11 Spring Boot Kotlin. Я использую версию 2.4.1 Spring Boot, мой импорт pom выглядит следующим образом
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.7.0</version>
<scope>test</scope>
</dependency>
Когда я запускаю свои тесты, я получаю следующую ошибку
java.lang.NullPointerException: Parameter specified as non-null is null: method net.app.service.smelter.SmelterMapper.toDtoPage, parameter entityPage
at net.app.service.smelter.SmelterMapper.toDtoPage(SmelterDto.kt)
at net.app.web.SmelterControllerTest.testListAllSmelters_Success1ItemReturned(SmelterControllerTest.kt:124)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:54)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:99)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:105)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:40)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
,,,
Мне было интересно, как лучше всего протестировать мой класс контроллера — вот что у меня есть на данный момент
SmelterControllerTest
package net.app.web
import net.web.domain.Smelter
import net.web.service.smelter.SmelterDto
import net.web.service.smelter.SmelterMapper
import net.web.service.smelter.SmelterService
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultHandlers
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import java.util.*
@WebMvcTest(controllers = [SmelterController::class], )
internal class SmelterControllerTest @Autowired constructor(
private val mockMvc: MockMvc,
) {
companion object {
private val BASE_ENDPOINT = "/api/smelter"
}
@MockBean
private lateinit var smelterService: SmelterService
@MockBean
private lateinit var smelterMapper: SmelterMapper
@Test
fun `list all smelters returns one item`() {
val smelterList = LinkedList<Smelter>()
smelterList.add(Smelter())
val smelters = PageImpl(smelterList)
val smelterDtoList = LinkedList<SmelterDto>()
val smelterDto = SmelterDto()
smelterDto.id = "id"
smelterDto.name = "name"
smelterDto.metal = "value"
smelterDtoList.add(smelterDto)
val smelterDtos = PageImpl(smelterDtoList)
Mockito.`when`(smelterService.listAll(Mockito.isA(Pageable::class.java))).thenReturn(smelters)
Mockito.`when`(smelterMapper.toDtoPage(Mockito.isA(Page::class.java) as Page<Smelter>, Mockito.isA(Pageable::class.java))).thenReturn(smelterDtos)
mockMvc.perform(
MockMvcRequestBuilders.get("$BASE_ENDPOINT/list?page=1amp;size=19amp;sort=id")
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().is2xxSuccessful)
.andExpect(MockMvcResultMatchers.jsonPath("$").exists())
.andExpect(MockMvcResultMatchers.jsonPath("$.content").exists())
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].id").value(smelterDtoList.get(0).id.toString()))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].name").value(smelterDtoList.get(0).name.toString()))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].metal").value(smelterDtoList.get(0).metal.toString())).andReturn()
Mockito.verify(smelterService , Mockito.times(1)).listAll(Mockito.isA(Pageable::class.java))
Mockito.verify(smelterMapper , Mockito.times(1)).toDtoPage(Mockito.isA(Page::class.java) as Page<Smelter>, Mockito.isA(Pageable::class.java))
}
}
SmelterController
package net.app.web
import net.app.service.smelter.SmelterDto
import net.app.service.smelter.SmelterMapper
import net.app.service.smelter.SmelterService
import org.slf4j.LoggerFactory
import org.springdoc.api.annotations.ParameterObject
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.http.MediaType
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping(path = ["/api/smelter/"], produces = [MediaType.APPLICATION_JSON_VALUE])
@CrossOrigin
class SmelterController(private val smelterService: SmelterService,
private val smelterMapper: SmelterMapper) {
companion object {
private val logger = LoggerFactory.getLogger(SmelterController::class.java)
}
/**
* List all smelters
*/
@GetMapping("list")
@Transactional(readOnly = true)
fun listAll(@ParameterObject pageable: Pageable): Page<SmelterDto> {
val entityPage = smelterService.listAll(pageable)
return smelterMapper.toDtoPage(entityPage, pageable)
}
}
SmelterServiceImpl
package net.app.service.smelter
import net.app.domain.Smelter
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
interface SmelterService {
fun listAll(pageable: Pageable): Page<Smelter>
}
@Service
class SmelterServiceImpl(
private val smelterRepository: SmelterRepository
) : SmelterService {
@Transactional(readOnly = true)
override fun listAll(pageable: Pageable): Page<Smelter> {
return smelterRepository.findAll(pageable)
}
}
SmelterRepository
package net.app.service.smelter
import net.app.domain.Grievance
import net.app.domain.Smelter
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
interface SmelterRepository : JpaRepository<Smelter, String> {
}
SmelterDto
package net.app.service.smelter
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import net.app.domain.Smelter
import net.app.service.organization.OrganizationMapper
import org.mapstruct.Mapper
import org.mapstruct.Mapping
import org.mapstruct.Mappings
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
class SmelterDto {
@JsonProperty
var id: String? = null
@JsonProperty
var name: String? = null
@JsonProperty
var metal: String? = null
@JsonProperty("location")
var countryLocation: String? = null
@JsonProperty
var lastAuditDate: String? = null
@JsonProperty
var auditCycle: String? = null
@JsonIgnore
var rmapEligibility: String? = null
@JsonIgnore
var rmapAuditStatus: String? = null
@JsonIgnore
var dbLastUpdate: String? = null
@JsonIgnore
var organizationId: Long? = null
}
@Mapper(componentModel = "spring",
uses = [
OrganizationMapper::class
])
abstract class SmelterMapper {
@Mappings(
Mapping(source = "organization.id", target = "organizationId")
)
abstract fun toDto(smelter: Smelter): SmelterDto
abstract fun toDtoList(entityList: List<Smelter>): List<SmelterDto>
fun toDtoPage(entityPage: Page<Smelter>, pageable: Pageable): Page<SmelterDto> {
val dtoList = this.toDtoList(entityPage.content)
return PageImpl(dtoList, pageable, entityPage.totalElements)
}
@Mappings(
Mapping(source = "organizationId", target = "organization.id")
)
abstract fun toEntity(smelterDto: SmelterDto): Smelter
}
Буду очень признателен за любую помощь в правильном тестировании этого класса
Комментарии:
1. В качестве примечания я настоятельно рекомендую никогда не использовать
standaloneSetup
его. Чрезвычайно легко настроить тестовую настройку, немного отличающуюся от вашей реальной настройки (скажем, установить параметр сериализации JSONapplication.yml
), и, как вы можете видеть, вы тратите огромное количество кода, имитирующего сценарий.@SpringBootTest
это немного медленнее, но намного дешевле по времени разработки.2. Обновленный тест в соответствии с рекомендациями — все еще получаю эти ошибки — org.mockito.exceptions.misusing. Исключение InvalidUseOfMatchersException: здесь обнаружен неуместный или неправильно используемый сопоставитель аргументов:
Ответ №1:
Весь тестовый класс кажется мне немного запутанным… Вы используете @EnableSpringDataWebSupport @RunWith(MockitoJUnitRunner::class)
аннотации. Стандартный способ тестирования вашего контроллера — использовать @WebMvcTest
аннотацию — см. https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests.
Тогда ваш тестовый класс должен выглядеть примерно так:
@WebMvcTest(controllers = [SmelterController::class])
internal class SmelterControllerTest @Autowired constructor(
private val mockMvc: MockMvc,
) {
@MockBean
private lateinit var smelterService: SmelterService
@MockBean
private lateinit var smelterMapper: SmelterMapper
@Test
fun `list all smelters returns one item`() {
// mock behaviour of smelterService.listAll
// mock behaviour of smelterMapper.toDtoPage
// perform mockMvc call
// verify mocks
}
}
Комментарии:
1. Спасибо @Eduard Tomek. Это намного аккуратнее. Я обновил свой тест, но у меня все еще есть некоторые насмешливые проблемы с сопоставителями аргументов
2. Следовали этому подходу. Я удалил mocking для классов mapstruct, и теперь все тесты запущены. Кроме того, код намного аккуратнее благодаря Эдуарду