Mockito с классом контроллера Kotlin

#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 его. Чрезвычайно легко настроить тестовую настройку, немного отличающуюся от вашей реальной настройки (скажем, установить параметр сериализации JSON application.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, и теперь все тесты запущены. Кроме того, код намного аккуратнее благодаря Эдуарду