Search code examples
kotlinunit-testingjpaspring-data-jpaauto-increment

inject id into JPA entity for unit testing


With Spring Boot 3 JPA (Kotlin), I have an entity with an id property that is auto-generated. Now I have code which converts such an entity to a DTO (and the id is important in that context). How can I inject an id into the entity to unit test that code? (I don't want to use a heavyweight test with database). The constructor does not allow for passing the id, and I don't want to change the production code.

@Entity
@Table(name = "users")
data class User(
@Column(name = "created_at", nullable = false)
    val createdAt: Instant,
){
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private var id: Int = 0
}

** EDIT: ** I noticed that in the original question the id field was not private. Then I can just assign the id from the outside during my test. I intended to ask about when the id is private, which is the only interesting scenario. I updated the code example accordingly. Sorry for the glitch.


Solution

  • My circumstance is different, but you should be able to take ideas from this approach.

    • I am using Mongo, but still Spring Data / JPA
    • With my case we generate ids on the client, not the server, but still you can still intercept the Entity and set the Id
    • I am using MockK, a Kotlin mocking library. You don't say what you are using, but again the principles transferable to Mockito, etc.

    When instantiating the test class, the following function creates a mock Respository which for my purposes simply stores entities in a MutableMap, with just one other function mocked, the findById(..)

    EDIT: following OP's edit You could use reflection to set the private field. Code sample below updated:

    private val savedAppointmentsById = mutableMapOf<RULID, MemberAppointment>()
    
    private fun createSemiStatefulAppointmentRepositoryMock(): AppointmentRepository {
        val repository: AppointmentRepository = mockk(relaxed = false)
        savedAppointmentsById.clear()
    
        every { repository.save(any()) } answers {
            val appointment = firstArg<MemberAppointment>()
            // here you would do something like:
            val id = "new id"
            appointment::class.java.getDeclaredField("id").let {
                it.isAccessible = true
                it.set(appointment, id)
            }
            savedAppointmentsById.put(appointment.id, appointment)
            appointment
        }
    
        every { repository.findById(any()) } answers {
            Optional.ofNullable(savedAppointmentsById.get(firstArg<RULID>()))
        }
        return repository
    }