我尝试在 Spring boot + Kotlin 上的简单 CRUD 应用程序中进行单元测试。 我已经对其他实体和存储库进行了测试,但用户实体和存储库存在问题。 该表链接到表 Team,可能这就是为什么我的测试的标准逻辑不起作用的原因。
Springboot版本“3.2.4”
实体
@Entity
@Table(name = "\"user\"")
@DynamicUpdate
class UserEntity(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_generator")
@SequenceGenerator(name = "user_generator", sequenceName = "user_id_seq", allocationSize = 1)
@Column(name = "id")
var userId: Int?,
@Column(name = "email")
val email: String,
@Enumerated(EnumType.STRING)
@Column(name = "role", columnDefinition = "user_role_enum", nullable = false)
val role: Role,
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "user_teams_team",
joinColumns = [JoinColumn(name = "user_id")],
inverseJoinColumns = [JoinColumn(name = "team_id")]
)
val teams: Set<TeamEntity>? = null
)
@Entity
@Table(name = "team")
@DynamicUpdate
class TeamEntity(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "team_generator")
@SequenceGenerator(name = "team_generator", sequenceName = "team_id_seq", allocationSize = 1)
@Column(name = "id")
var teamId: Int?,
@Column(name = "name_ru")
val nameRu: String,
@Column(name = "name_en")
val nameEn: String,
@Column(name = "name_kk")
val nameKk: String
)
DTO
class UserDto(
val email: String,
val role: Role,
val teams: List<Int>?
)
data class UserResponseDto(
val userId: Int,
val email: String,
val role: Role,
var teams: Set<TeamEntity>?
)
用户服务实现
override fun createUser(userDto: UserDto): UserResponseDto {
checkIfSuchTeamsExistOrThrowNotFoundException(userDto)
val teamSet = teamServiceImpl.createTeamSetFromTeamIdsOrThrowNotFoundException(userDto)
val savedUser = userRepository.save(userMapper.toEntity(userDto, teamSet))
return userMapper.toDto(savedUser)
}
private fun checkIfSuchTeamsExistOrThrowNotFoundException(userDto: UserDto) {
val existingInDbTeamIdList = teamRepository.findAll().map { it.teamId!! }
userDto.teams?.forEach { teamId ->
if (teamId !in existingInDbTeamIdList) {
throw NotFoundException("There is no team with id: $teamId")
}
}
}
团队服务实现
fun createTeamSetFromTeamIdsOrThrowNotFoundException(userDto: UserDto): MutableSet<TeamEntity> {
val teamSet: MutableSet<TeamEntity> = mutableSetOf()
userDto.teams?.forEach { teamId ->
val teamEntity = teamRepository.findById(teamId)
.orElseThrow { NotFoundException("There is no team with id: $teamId") }
teamSet.add(teamEntity)
}
return teamSet
}
UserServiceImplTests
@ExtendWith(SpringExtension::class)
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
class UserServiceImplTests {
@Mock
private lateinit var teamMapper: TeamMapper
@Mock
private lateinit var userRepository: UserRepository
@Mock
private lateinit var teamRepository: TeamRepository
@Mock
private lateinit var teamService: TeamServiceImpl
@Mock
private lateinit var userMapper: UserMapper
@InjectMocks
private lateinit var userService: UserServiceImpl
@Captor
private lateinit var userEntityCaptor: ArgumentCaptor<UserEntity>
@Test
fun createUser_Success() {
val id = 1
val teamEntity = TeamEntity(id, "testRu", "testEn", "testKk")
val userEntity = UserEntity(id, "test", Role.OWNER, setOf(teamEntity))
val userEntity1 = UserEntity(id, "test", Role.OWNER, setOf(teamEntity))
val teamSet = mutableSetOf(teamEntity)
val userDto = UserDto("test", Role.OWNER, listOf(1))
val userResponseDto = UserResponseDto(id, "test", Role.OWNER, setOf(teamEntity))
whenever(teamRepository.findAll()).thenReturn(listOf(teamEntity))
whenever(teamRepository.findById(id)).thenReturn(Optional.of(teamEntity))
whenever(userMapper.toEntity(userDto, teamSet)).thenReturn(userEntity)
whenever(userRepository.save(userEntity)).thenReturn(userEntity)
whenever(userMapper.toDto(userEntity)).thenReturn(userResponseDto)
val result = userService.createUser(userDto)
assertEquals(userResponseDto, result)
verify(userRepository).save(userEntityCaptor.capture())
val capturedEntity = userEntityCaptor.value
assertNotNull(capturedEntity)
assertEquals(userEntity, capturedEntity)
verify(userRepository).save(userEntity)
verify(teamRepository).findAll()
}
这就是错误本身:
save(...) must not be null
java.lang.NullPointerException: save(...) must not be null
at kt.cell.jobsapi.service.impl.UserServiceImpl.createUser(UserServiceImpl.kt:39)
at kt.cell.jobsapi.service.impl.UserServiceImplTests.createUser_Success(UserServiceImplTests.kt:123)
我只是不明白去哪里寻找
您在测试代码周围使用mockito-kotlin,但对ArgumentCaptor使用普通mockito。
ArgumentCaptor.capture返回其泛型参数UserEntity的默认值,即null。
要解决此问题,请使用mockito-kotlin 中的捕获,它会为对象创建通用参数的新实例,以防止 NPE。
参见相关定义ArgumentCaptor.kt
inline fun <reified T : Any> capture(captor: ArgumentCaptor<T>): T {
return captor.capture() ?: createInstance()
}