带有 Kotlin 控制器的 Mockito class

Mockito with Kotlin Controller class

我正在尝试使用 mockito 版本 3.7.0 来测试我的 Java 11 Spring Boot Kotlin 控制器。我正在使用 Spring Boot 的 2.4.1 版 我的 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.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.evaluate(DefaultInternalRunner.java:54)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner.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.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access0(ParentRunner.java:66)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.mockito.internal.runners.DefaultInternalRunner.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)

,

我想知道测试我的控制器的最佳方法是什么class - 这是我现在所拥有的

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=1&size=19&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))
        }
    }

冶炼厂控制器

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
}

如果能帮助我们正确测试 class,我们将不胜感激

整个测试 class 对我来说似乎有点混乱...您正在使用 @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.

然后你的测试 class 应该看起来像这样:

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