Observe property change in Spring tests

56 views Asked by At

Consider a Service-Bean, that sets a property on some schedule:

@Service
class UserService {
    var userInfo: Map<String, UserInfo>? = null

    @Scheduled(...)  
    private fun fetchUserInfo() {
        userInfo = callExternalUserEndpoint()
    }
}

For testing purposes, I would like to access UserService::userInfo once it has been set by the scheduled function.

My solution so far is to repeatedly call sleep():

@SpringBootTest
class UserServiceTest {

    @Autowired    
    lateinit var userService: UserService    

    @BeforeEach    
    fun beforeEach() {
        // make sure userInfo not null      
        while (userService.userInfo == null) {
            Thread.sleep(50)
        }
    }

    @Test
    fun someTest() {
        // access userService.userInfo ...    
    }
}

I have the feeling, that there is a concept or pattern in Kotlin, that achieves this objective in a standardized and elegant way. From consulting the Kotlin documentation, it seems to me, that one should be able to do this using delegated properties, Delegates.observable() or a combination of the two. However, I have been unable to make it work. A delegated getter for the lateinit variable could look like:

@Component
class Delegate {
    @Autowired    
    lateinit var userService: UserService    

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return userService.userInfo.toString()
    }
}

Using Delegates.observable() , a variable in the test scope can be defined and its changes monitored. How does one connect the two? Something like Delegates.observable() , but with a custom getter, that accesses userService.userInfo under the hood?

1

There are 1 answers

0
DFPy On

Following Tenfour04's advice, I refactored UserService, but without using Kotlin coroutines:

@Service
class UserService {
    var userInfo: Map<String, UserInfo>? = null
        get() {
            if (field == null) {
                fetchUserInfo()
            }
            return field
        }

    @Scheduled(...)  
    private fun fetchUserInfo() {
        userInfo = callExternalUserEndpoint()
    }
}

This way, accessing userInfo guarantees that a call to fetchUserInfo() has finished before a value is returned, the initialization is encapsulated in UserService and an external scope does not have to concern itself with the initialization.