spring-bootunit-testingmockito

save(...) must not be null in spring boot tests with Kotlin


I try to do unit tests in simple CRUD app on Spring boot + Kotlin. I already did the tests for other entities and repos but have problems with User entity and repo.

That table is linked to table Team, probably that is why standard logic of my tests does not work.

Springboot version "3.2.4"

Entity

@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>?
)

UserServiceImpl

package kt.cell.jobsapi.service.impl

import kt.cell.jobsapi.exception.InternalServerException
import kt.cell.jobsapi.exception.NotFoundException
import kt.cell.jobsapi.model.dto.*
import kt.cell.jobsapi.model.mapper.UserMapper
import kt.cell.jobsapi.repository.UserRepository
import kt.cell.jobsapi.service.UserService
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.stereotype.Service
import java.util.*


@Service
class UserServiceImpl(
    val userRepository: UserRepository,
    val userMapper: UserMapper,
    val teamServiceImpl: TeamServiceImpl
) : UserService {

@Throws(NotFoundException::class)
override fun getUsers(): List<UserResponseDto> {
    val userEntityList = userRepository.findAll()

    return if (userEntityList.isNotEmpty()) {
        userEntityList.map { userMapper.toDto(it) }
    } else {
        throw NotFoundException("No users found")
    }
}

@Throws(Exception::class, DataIntegrityViolationException::class, NotFoundException::class)
override fun createUser(userDto: UserDto): UserResponseDto {
    teamServiceImpl.checkIfSuchTeamsExistOrThrowNotFoundException(userDto)

    val teamSet = teamServiceImpl.createTeamSetFromTeamIdsOrThrowNotFoundException(userDto)
    val savedUser = userRepository.save(userMapper.toEntity(userDto, teamSet))
    return userMapper.toDto(savedUser)
}

TeamServiceImpl

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
}
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")
        }
    }
}

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()
}

THis is the error itself:

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)

I just dont understand where to look for


Solution

  • You don't stub teamServiceImpl.createTeamSetFromTeamIdsOrThrowNotFoundException(userDto) which is why it returns null. In consequence arguments passed to toEntity don't match so it returns null as well.