How to verify/test WebClient usage - spring-webflux

I need to unit test a class that uses the WebClient. Is there any good way to deal with the WebClient?
With the RestTemplate I could easily use Mockito. Mocking the WebClient is a bit tedious, since deep stubs don't work with the webclient...
I want to test if my code provides the correct headers...
shortened sample code:
public class MyOperations {
private final WebClient webClient;
public MyOperations(WebClient webClient) {
this.webClient = webClient;
}
public Mono<ResponseEntity<String>> get( URI uri) {
return webClient.get()
.uri(uri)
.headers(computeHeaders())
.accept(MediaType.APPLICATION_JSON)
.retrieve().toEntity(String.class);
}
private HttpHeaders computeHeaders() {
...
}
}

This is aimed to unit, not integration tests...
Implemented in Kotlin, this is a bit rudimentary, but it's effective. The idea can be extracted from this pieces of code below
First, a WebClient kotlin extension
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito.*
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.WebClientResponseException
import reactor.core.publisher.toMono
fun WebClient.mockAndReturn(data: Any) {
val uriSpec = mock(WebClient.RequestBodyUriSpec::class.java)
doReturn(uriSpec).`when`(this).get()
doReturn(uriSpec).`when`(this).post()
...
val headerSpec = mock(WebClient.RequestBodyUriSpec::class.java)
doReturn(headerSpec).`when`(uriSpec).uri(anyString())
doReturn(headerSpec).`when`(uriSpec).uri(anyString(), anyString())
doReturn(headerSpec).`when`(uriSpec).uri(anyString(), any())
doReturn(headerSpec).`when`(headerSpec).accept(any())
doReturn(headerSpec).`when`(headerSpec).header(any(), any())
doReturn(headerSpec).`when`(headerSpec).contentType(any())
doReturn(headerSpec).`when`(headerSpec).body(any())
val clientResponse = mock(WebClient.ResponseSpec::class.java)
doReturn(clientResponse).`when`(headerSpec).retrieve()
doReturn(data.toMono()).`when`(clientResponse).bodyToMono(data.javaClass)
}
fun WebClient.mockAndThrow() {
doThrow(WebClientResponseException::class.java).`when`(this).get()
doThrow(WebClientResponseException::class.java).`when`(this).post()
...
}
Then, the unit test
class MyRepositoryTest {
lateinit var client: WebClient
lateinit var repository: MyRepository
#BeforeEach
fun setUp() {
client = mock(WebClient::class.java)
repository = MyRepository(client)
}
#Test
fun getError() {
assertThrows(WebClientResponseException::class.java, {
client.mockAndThrow()
repository.get("x")
})
}
#Test
fun get() {
val myType = MyType()
client.mockAndReturn(myType)
assertEquals(myType, repository.get("x").block())
}
}
Note: tests on JUnit 5

This will be supported in a future Spring Framework version with MockRestServiceServer; see SPR-15286

Related

Mock class used in a method Kotlin

I am using mockk Kotlin library. I have a service Service that has a private method calling a 3d party client
class Service {
fun foo() {
bar()
}
private fun bar() {
client = Client()
client.doStuff()
}
}
Now in my test I need to mock Client, e.g.
#Test
fun `my func does what I expect` {
}
I need to also mock what doStuff returns. How do I achieve this in Kotlin mockk?
Firstly, you should never instantiate a dependency like Client inside your service class since you cannot access it to provide a Mock. Let's deal with that first...
class Client { // this is the real client
fun doStuff(): Int {
// access external system/file/etc
return 777
}
}
class Service(private val client: Client) {
fun foo() {
bar()
}
private fun bar() {
println(client.doStuff())
}
}
and then this how to use Mockk
class ServiceTest {
private val client: Client = mockk()
#Test
fun `my func does what I expect`() {
every { client.doStuff() } returns 666
val service = Service(client)
service.foo()
}
}

Test case is calling actual method even after mocking the method call in ktor framework (kotlin)

I am testing an API written in Kotlin using the KTOR framework. For the testing, I am using JUnit5 and Mockito. There is a route class where a route is defined which I need to test. Here is the route class :-
fun Application.configureRouting() {
routing {
post("/someRoute") {
val service = MyService()
val request: JsonNode = call.receive()
launch {
service.dummyFunction(request)
}
val mapper = ObjectMapper()
val responseStr = "{\"status\":\"success\",\"message\":\"Request has been received successfully\"}"
val response: JsonNode = mapper.readTree(responseStr)
call.fireHttpResponse(HttpStatusCode.OK, response)
}
}
}
This is the test case I am writing for it :-
class RouteTest {
#Mock
var service = MyService()
// read the configuration properties
private val testEnv = createTestEnvironment {
config = HoconApplicationConfig(ConfigFactory.load("application.conf"))
}
#Before
fun setUp() = withApplication(testEnv) {
MockitoAnnotations.openMocks(MyService::class)
}
#Test
fun test() = withApplication(testEnv) {
withTestApplication(Application::configureRouting) {
runBlocking {
Mockito.`when`(service.dummyFunction(Mockito.any()).thenReturn(true)
with(handleRequest(HttpMethod.Post, "/someRoute") {
setBody("some body")
}) {
assertEquals(HttpStatusCode.OK, response.status())
}
}
}
}
}
When I run the test, it calls the actual "dummyFunction()" method instead of the mocked one and hence, it is failing. Am I doing something wrong?
Because your service in test is different from the service you mocked. To solve this, you need to inject the service into your class, or pass the service as an argument.
Read more: IoC, DI.
The simplest way to solve your problem is to define the service parameter for the configureRouting method and pass a corresponding argument in the test and production code when calling it.
fun Application.configureRouting(service: MyService) {
routing {
post("/someRoute") {
val request: JsonNode = call.receive()
launch {
service.dummyFunction(request)
}
val mapper = ObjectMapper()
val responseStr = "{\"status\":\"success\",\"message\":\"Request has been received successfully\"}"
val response: JsonNode = mapper.readTree(responseStr)
call.fireHttpResponse(HttpStatusCode.OK, response)
}
}
}
class RouteTest {
#Mock
var service = MyService()
private val testEnv = createTestEnvironment {
config = HoconApplicationConfig(ConfigFactory.load("application.conf"))
}
#Test
fun test() = withApplication(testEnv) {
withTestApplication({ configureRouting(service) }) {
runBlocking {
// Your test...
}
}

Dagger2 Use a Provider<T> for #BindsInstance

Is it possible to bind a Provider<T> instead of just an instance T?
For example, I have this:
#Component()
interface Module {
#Component.Builder
interface Builder {
#BindsInstance
fun config(config: Config): Builder
}
}
I would like to do something like this (but it doesn't work):
#Component()
interface Module {
#Component.Builder
interface Builder {
#BindsInstance
fun config(config: Provider<Config>): Builder
}
}
What am I trying to do?
NOTE: The Config class is immutable
I would like to be able to change the config value for different tests, e.g.
class Test {
val configProvider: CustomProvider<Config>
val classUnderTest: Provider<T>
#Before
fun setUp() {
val module = DaggerModule()
.setConfig(configProvider)
.build()
// Do other setup with module
classUnderTest = module.getTestClassProvider()
}
#Test
fun test() {
configProvider.setValue(CONFIG_1)
classUnderTest.get().doSomething();
}
#Test
fun test2() {
configProvider.setValue(CONFIG_2)
classUnderTest.get().doSomething();
}
}
I'm trying to avoid having to finish building the module in each test case and then calling another function to handle finishing the setup, e.g.
#Before
fun setUp() {
moduleBuilder = DaggerModule()
}
#Test
fun test() {
val module = moduleBuilder.setConfig(CONFIG_1).build()
finishSetup(module)
// Do test
}
Thanks for your help!

Issue compiling with dependency injection

Error with Kotlin conversion from java, this project is testing Github Api and displaying data in recyclerview.I get the a compile error which i cannot workout for Dagger2, it worked in Java but when converting to Kotlin i get a compile error at runtime.
It seems to be with injecting an api method into the view model
i have tried following the error then cleaning and rebuilding the app
I have also tried invalidating caches and restarting but seems there is an error with the conversion into Kotlin from Java. Any help would be appreciated.
Here is my class:
class RepoRepository {
private lateinit var repoService: GithubRepos
#Inject
fun RepoRepository(repoService: GithubRepos) {
this.repoService = repoService
}
fun getRepositories(): Single<List<Repo>> {
return repoService.getRepos()
}
fun getSingleRepo(owner: String, name: String): Single<Repo> {
return repoService.getSingleRepo(owner, name)
}
}
My component class:
#Singleton
#Component(modules = arrayOf(NetworkModule::class))
interface AppComponent {
/**
* inject required dependencies into MainActivityListViewModel
*/
fun inject(mainActivityListViewModel: MainActivityListViewModel)
#Component.Builder
interface Builder {
fun build(): AppComponent
fun networkModule(networkModule: NetworkModule): Builder
}
}
And my ViewModel:
class MainActivityListViewModel : BaseViewModel() {
private lateinit var repoRepository: RepoRepository
private var disposable: CompositeDisposable? = null
private val repos = MutableLiveData<List<Repo>>()
private val repoLoadError = MutableLiveData<Boolean>()
private val loading = MutableLiveData<Boolean>()
#Inject
fun ListViewModel(repoRepository: RepoRepository) {
this.repoRepository = repoRepository
disposable = CompositeDisposable()
fetchRepos()
}
fun getRepos(): LiveData<List<Repo>> {
return repos
}
fun getError(): LiveData<Boolean> {
return repoLoadError
}
fun getLoading(): LiveData<Boolean> {
return loading
}
private fun fetchRepos() {
loading.value = true
disposable?.add(repoRepository.getRepositories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object :
DisposableSingleObserver<List<Repo>>() {
override fun onSuccess(value: List<Repo>) {
repoLoadError.value = false
repos.value = value
loading.value = false
}
override fun onError(e: Throwable) {
repoLoadError.value = true
loading.value = false
}
}))
}
override fun onCleared() {
super.onCleared()
if (disposable != null) {
disposable!!.clear()
disposable = null
}
}
}
this is error i am getting:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides-annotated
method. This type supports members injection but cannot be
implicitly provided.
public abstract repos.network.RepoRepository
repoRepository();
^
repos.network.RepoRepository is provided at
components.AppComponent.repoRepository() e: repos/di/components/AppComponent.java:19: error:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides- annotated
method. This type supports members injection but cannot be
implicitly provided.
^
repos.network.RepoRepository is injected at
repos.viewmodels.MainActivityListViewModel.ListViewModel(repoRepository)
repos.viewmodels.MainActivityListViewModel is injected at
repos.di.components.AppComponent.inject(repos.viewmodels.MainActivityListViewModel)
Your error clearly says:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be provided without an #Inject constructor or an #Provides-annotated method.
You didn't define constructor for your RepoRepository class.
It should look something like this:
class RepoRepository #Inject constructor(private val repoService: GithubRepos) {//the rest of your code here}
This goes for your viewmodel class as well.
If you are using android ViewModel architecture component i suggest you read this article which explains how to use it with Dagger2.
Hope this helps.

Mocking ViewModel in Espresso

I'm writing Espresso UI test which mocks viewModel, referring GithubBrowserSample
what is the use of "TaskExecutorWithIdlingResourceRule", declaring Junit Rule will take care of IdlingResource?
Even after referring this "TaskExecutorWithIdlingResourceRule" class in my project whenever I build, compiler doesn't throw any error but when I run the test case it shows the Unresolved Error(s)
TaskExecutorWithIdlingResourceRule.kt
import androidx.arch.core.executor.testing.CountingTaskExecutorRule
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import org.junit.runner.Description
import java.util.UUID
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
class TaskExecutorWithIdlingResourceRule : CountingTaskExecutorRule() {
// give it a unique id to workaround an espresso bug where you cannot register/unregister
// an idling resource w/ the same name.
private val id = UUID.randomUUID().toString()
private val idlingResource: IdlingResource = object : IdlingResource {
override fun getName(): String {
return "architecture components idling resource $id"
}
override fun isIdleNow(): Boolean {
return this#TaskExecutorWithIdlingResourceRule.isIdle
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
callbacks.add(callback)
}
}
private val callbacks = CopyOnWriteArrayList<IdlingResource.ResourceCallback>()
override fun starting(description: Description?) {
IdlingRegistry.getInstance().register(idlingResource)
super.starting(description)
}
override fun finished(description: Description?) {
drainTasks(10, TimeUnit.SECONDS)
callbacks.clear()
IdlingRegistry.getInstance().unregister(idlingResource)
super.finished(description)
}
override fun onIdle() {
super.onIdle()
for (callback in callbacks) {
callback.onTransitionToIdle()
}
}
}
Mocktest
#RunWith(AndroidJUnit4::class)
class MockTest {
#Rule
#JvmField
var activityRule = IntentsTestRule(SingleFragmentActivity::class.java, true, true)
#Rule
#JvmField
val executorRule = TaskExecutorWithIdlingResourceRule()
private lateinit var viewModel: SeriesFragmentViewModel
private val uiModelList = mutableListOf<SeriesBaseUIModel>()
private val seriesMutableLiveData = MutableLiveData<List<SeriesBaseUIModel>>()
private val seriesFragment = SeriesFragment()
#Before
fun init(){
viewModel = mock(SeriesFragmentViewModel::class.java)
`when`(viewModel.seriesLiveData).thenReturn(seriesMutableLiveData)
ViewModelUtil.createFor(viewModel)
activityRule.activity.setFragment(seriesFragment)
EspressoTestUtil.disableProgressBarAnimations(activityRule)
}
#Test
fun testLoading()
{
//Thread.sleep(3000)
uiModelList.add(ProgressUIModel())
seriesMutableLiveData.postValue(uiModelList.toList())
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText(R.string.pod_series_header_title_text)))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_description), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText("Hello")))
Thread.sleep(5000)
}
}