【发布时间】:2020-07-13 19:57:32
【问题描述】:
当我使用带有 date 字段 (LocalDate) 的 data class 时,我在 kotlin 中使用 POST 类型的测试(模拟)时遇到问题。
这是我正在使用的堆栈:
springBoot : v2.1.7.RELEASE
Java : jdk-11.0.4
kotlinVersion : '1.3.70'
junitVersion : '5.6.0'
junit4Version : '4.13'
mockitoVersion : '3.2.4'
springmockk : '1.1.3'
当我在应用程序中执行 POST 方法时,一切正常,我有响应并且数据正确保存在数据库中:
curl -X POST "http://127.0.1.1:8080/v1/person/create" -H "accept: */*" -H "Content-Type: application/json" -d "[ { \"available\": true, \"endDate\": \"2090-01-02\", \"hireDate\": \"2020-01-01\", \"id\": 0, \"lastName\": \"stringTest\", \"name\": \"stringTest\", \"nickName\": \"stringTest\" }]"
但是当我尝试对 POST 方法进行测试时,我不能(仅使用 POST 方法,使用 GET 即可)
这是我使用的类:
文件 Person.kt
@Entity
data class Person(
@Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.AUTO)
var id: Long,
var name: String,
var lastName: String,
var nickName: String,
@JsonFormat(pattern = "yyyy-MM-dd")
var hireDate: LocalDate,
@JsonFormat(pattern = "yyyy-MM-dd")
var endDate: LocalDate,
var available: Boolean
) {
constructor() : this(0L, "Name example",
"LastName example",
"Nick example",
LocalDate.of(2020,1,1),
LocalDate.of(2090,1,1),
true)
文件 PersonService.kt
@Service
class PersonService(private val personRepository: PersonRepository) {
fun findAll(): List<Person> {
return personRepository.findAll()
}
fun saveAll(personList: List<Person>): MutableList<person>? {
return personRepository.saveAll(personList)
}
}
文件 PersonApi.kt
@RestController
@RequestMapping("/v1/person/")
class PersonApi(private val personRepository: PersonRepository) {
@Autowired
private var personService = PersonService(personRepository)
@PostMapping("create")
fun createPerson(@Valid
@RequestBody person: List<Person>): ResponseEntity<MutableList<Person>?> {
print("person: $person") //this is only for debug purpose only
return ResponseEntity(personService.saveAll(person), HttpStatus.CREATED)
}
}
最后
PersonApiShould.kt(这个类是问题所在)
@EnableAutoConfiguration
@AutoConfigureMockMvc
@ExtendWith(MockKExtension::class)
internal class PersonApiShould {
private lateinit var gsonBuilder: GsonBuilder
private lateinit var gson: Gson
lateinit var mockMvc: MockMvc
@MockkBean
lateinit var personService: PersonService
@BeforeEach
fun setUp() {
val repository = mockk<PersonRepository>()
personService = PersonService(repository)
mockMvc = standaloneSetup(PersonApi(repository)).build()
gson = GsonBuilder()
.registerTypeAdapter(Person::class.java, PersonDeserializer())
.create()
gsonBuilder = GsonBuilder()
}
@AfterEach
fun clear() {
clearAllMocks()}
@Test
fun `create person`() {
val newPerson = Person(1L,
"string", //name
"string", //lastName
"string", //nickname
LocalDate.of(2020, 1, 1), //hireDate
LocalDate.of(2090, 1, 2), //endDate
true) //available
val contentList = mutableListOf<Person>()
contentList.add(newPerson)
// also tried with
// every { personService.findAll() }.returns(listOf<Person>())
// every { personService.saveAll(mutableListOf<Person>())}.returns(Person())
every { personService.findAll() }.returns(contentList)
every { personService.saveAll(any()) }.returns(contentList)
/* didn't work either
val personJson = gsonBuilder.registerTypeAdapter(Date::class.java, DateDeserializer())
.create().toJson(newPerson)
*/
val content = "[\n" +
" {\n" +
" \"available\": true,\n" +
" \"endDate\": \"2090-01-02\",\n" +
" \"hireDate\": \"2020-01-01\",\n" +
" \"id\": 0,\n" +
" \"lastName\": \"string\",\n" +
" \"name\": \"string\",\n" +
" \"nickName\": \"string\"\n" +
" }\n" +
"]"
val httpResponse = mockMvc.perform(post("/v1/resto/person/create")
.content(content) //also tried with .content(contentList)
.contentType(MediaType.APPLICATION_JSON))
.andReturn()
// error, because, httpResponse is always empty
val personCreated: List<Person> = gson.fromJson(httpResponse.response.contentAsString,
object : TypeToken<List<Person>>() {}.type)
assertEquals(newPerson.name, personCreated.get(0).name)
}
Gson 在反序列化日期时遇到一些问题,这是一个解析器(hack),它适用于我的 GET 方法
文件 PersonDeserializer.kt
class PersonDeserializer : JsonDeserializer<Person> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Person {
json as JsonObject
val name = json.get("name").asString
val lastName = json.get("lastName").asString
val nickName = json.get("nickName").asString
val available = json.get("available").asBoolean
val hireDate = LocalDate.of((json.get("hireDate") as JsonArray).get(0).asInt,
(json.get("hireDate") as JsonArray).get(1).asInt,
(json.get("hireDate") as JsonArray).get(2).asInt)
val endDate = LocalDate.of((json.get("endDate") as JsonArray).get(0).asInt,
(json.get("endDate") as JsonArray).get(1).asInt,
(json.get("endDate") as JsonArray).get(2).asInt)
return Person(1L, name, lastName, nickName, hireDate, endDate, available)
}
}
我看到错误在 MOCKK 库中,因为通过测试我可以到达端点并正确打印值
print from endpoint: print("person: $person") //此行在端点中
Person: [Person(id=0, name=string, lastName=string, nickName=string, hireDate=2020-01-01, endDate=2090-01-02, available=true)]
错误测试日志
19:27:24.840 [主要] 调试 io.mockk.impl.recording.states.AnsweringState - 投掷 io.mockk.MockKException:没有找到答案: PersonRepository(#1).saveAll([Person(id=0, name=string, 姓=字符串,昵称=字符串,hireDate=2020-01-01, endDate=2090-01-02, available=true)]) on PersonRepository(#1).saveAll([Person(id=0, name=string, 姓=字符串,昵称=字符串,hireDate=2020-01-01, endDate=2090-01-02, available=true)])
19:27:24.844 [主要] 调试 org.springframework.test.web.servlet.TestDispatcherServlet - 失败 完整的请求:io.mockk.MockKException:找不到答案: PersonRepository(#1).saveAll([Person(id=0, name=string, 姓=字符串,昵称=字符串,hireDate=2020-01-01, endDate=2090-01-02, available=true)])
org.springframework.web.util.NestedServletException: 请求 处理失败;嵌套异常是 io.mockk.MockKException: no 找到的答案:PersonRepository(#1).saveAll([Person(id=0, 姓名=字符串,姓=字符串,昵称=字符串,hireDate=2020-01-01, endDate=2090-01-02, available=true)])
错误因修复而异,我也得到了
JSON 解析错误:无法从 ... 反序列化
java.time.LocalDate类型的值 ... 48 更多
但是在 Spring 中使用 Kotlin 序列化 LocalDate 总是同样的问题
如果您能提供任何帮助,我们将不胜感激。
【问题讨论】:
标签: spring spring-boot kotlin junit mockk