I am newbie to writing a test code as an Android developer.
I am using the Kotlin and Retrofit in my android app.
I have a retrofit interface like below:
#GET("/3/movie/{movieId}")
fun getMovie(#Query("api_key") apiKey: String, #Path("movieId") movieId: String): Single<TmdbMovieResp>
The response is "TmdbMovieResp".
And my test code is :
`when`(mockApi.getMovie(mockApiKey, "id")).thenReturn(Single.just(mockMovieResp))
This means I should make the "mockMovieResp".
But the "TmdbMovieResp" has too many member variables.
I can make it, but it's too boring!
And in this case it just one test.
If I have more methods and response types, I should do similar task again and again!
Is there any cool solution?
Here is different approach. You can use https://github.com/andrzejchm/RESTMock
More info by the link. Usage is very simple:
RESTMockServer.whenGET(pathContains("x/y/z/"))
.thenReturnString("your-response-json");
One thing you can do by using your Model classes in your Test method.
As you are using Retrofit you must have some converter like Moshi, GSON for response handling. If you have model classes then use those class for response mocking like below.
val success = TmdbMovieResp() //Response Model class
Then mock the rest api call with the success result like below.
`when`(mockApi.getMovie(mockApiKey, "id")).thenReturn(Single.just(success))
Instead of Mockito, use MockK for your mocking, then utilize relaxed mocks.
class MyClass {
fun foo() : ComplexObject { ... }
}
#Test
fun `test foo`() {
val myClass = spyk(MyClass())
val complex : ComplexObject = mockk(relaxed = true)
every { myClass.foo() } returns complex
assertEquals(complex, myClass.foo())
println(complex.someIntProperty) // prints 1
}
Relaxed mockks return canned data for any value or function, so that you don't need to specify all of those individual properties when they don't matter.
Related
I am new to mockk framework and trying to write a test case for below function
fun fromCityWeather(cityWeather: List<CityWeather>): HomeWeather {
return HomeWeather(
cityWeather.first().temperature,
cityWeather.first().description
)
}
CityWeather and HomeWeather are my 2 classes and both have temperature and weather as fields. This function is written inside companion object of another class.
Please help me to understand the logic to start with as I have tried multiple times writing the test case while referring the blogs on internet and none worked.
You don't need mockk:
#Test
fun testFromCityWeather() {
val weatherList = listOf(CityWeather("30", "degrees"), CityWeather("12", "degrees"))
val expected = HomeWeather("30", "degrees")
assertEquals(expected, fromCityWeather(weatherList))
}
(Assuming temperature and description are strings)
I'm trying to wrap my head around the mentioned components and I can't get it right. I want to do something very simple: Fetch data from the network and present it to the user. Currently am not yet caching it as am still learning new Coroutine features in Architecture components. Every time app loads I get an empty model posted, which seems weird.
My API is get hit fine and response is 200 which is OK.
Below is what I have attempted:
POJO
data class Profile(#SerializedName("fullname") val fullName : String.....)
Repository
class UserRepo(val context: Context, val api: Api) {
suspend fun getProfile(): Profile
{
val accessToken = ....
return api.getUserProfile(accessToken)
}
}
API
interface GatewayApi {
#GET("users/profile")
suspend fun getUserProfile(#Query("access-token") accessToken: String?): Profile
}
ViewModel
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val usersRepo = UserRepo(application.applicationContext, Apifactory.Api)
val userProfileData = liveData{
emit(usersRepo.getProfile())
}
fun getProfile() = viewModelScope.launch {
usersRepo.getProfile()
}
}
Finally my fragment's relevant code
val viewModel = ViewModelProviders.of(activity!!).get(UserViewModel::class.java)
viewModel.userProfileData.observe(this, Observer<UserProfile> {
//it is having nulls
})
//trigger change
viewModel.getProfile()
So I added HTTP requests and responses (thanks to #CommonsWare for pointing that out) and it happened I had used a different model than I was supposed to use. The correct model that mapped the JSON response was ProfileResponse and as you can see in my posted code, I used Profile instead. So all fields were empty as Gson could not correctly serialize JSON into Profile object.
All the credit goes to #CommonsWare for pointing that out in comment.
For Example I have a retrofit interface such as:
interface SampleService {
fun getSomething(#body someBody: SomeBody)
}
Now I have a class which uses this interface such as:
class UserRequester(val service: SampleService) {
fun doGetSomething(someValue: String) {
val response = service.getSomething(SomeBody(someValue))
// ...
}
}
I want to test this class but dont know how to mock it.
I'm trying the following:
val mockSampleService = mock()
val userRequester = UserRequester(mockSampleService)
val requestBody = SomeBody(someString))
when(mockSampleService.getSomething(requestBody)).return(myExpectedValue)
....
My problem is that since I create the request object inside the function, I could not make the mock when().thenReturn() to work since i am technically passing two different object.
How should I test this? Thanks in advance.
The mocking problem (UserRequester)
You are not able to mock the mockSampleService method because your class is creating the SomeBody object and is different from the SomeBody object you are creating in your test.
Now you have 2 options:
Use Mockito.any() in your test, in this way you basically say that whatever your method is gonna use as parameter you will return the mocked behaviour
Use a factory that given a someString returns you a SomeObject like this:
// the factory
class SomeObjectFactory{
fun createSomeObject(someString: String): SomeObject {
return SomeObject(someString)
}
}
//the class
class UserRequester(
val service: SampleService, val factory: SomeObjectFactory
) {
fun doGetSomething(someValue: String) {
val response = service.getSomething(factory.createSomeObject(someValue))
// ...
}
}
//the test
class MyTest{
#Test
fun myTestMethod(){
val mockSampleService = mock()
val factory = mock()
val someBody = mock()
val userRequester = UserRequester(mockSampleService, factory)
`when`(factory.createSomeObject(someString)).thenReturn(someBody)
`when`(mockSampleService.getSomething(someBody)).thenReturn(myExpectedValue)
//rest of the code
}
}
The second approach is the cleanest one.
Testing Retrofit calls (SampleService)
I wouldn't unit test a Retrofit call.
When you are dealing with frameworks, apis, databases, shared preferences is always preferable to do integration tests instead of unit tests.
In this way you are actually testing that your code is working with the outside world.
I suggest you to test Retrofit calls with MockWebServer (it's a library from Square, the same company that developed OkHttp and Retrofit).
This read may be also helpful.
Probably SomeBody is a plain value object, since Retrofit requests work with value objects. If you define the equals method for the SomeBody class then the eq matcher will work, and you can write using mockito-kotlin:
whenever(mockService.getSomething(eq(SomeBody(someString)))).thenReturn(stubbedResult)
Actually, you can omit the eq matcher, Mockito will use the equals method for matching.
If SomeBody is a Kotlin data class then the equals method is automatically defined by comparing the fields.
If for some reason you don't want to rely on equals, then you can use the argThat matcher defined in mockito-kotlin:
whenever(mockService.getSomething(argThat { theField == someValue })).thenReturn(stubbedResult)
The problem is that there is static dependency on SomeBody's constructor:
val response = service.getSomething(SomeBody(someValue))
What you could do to have control over the instantiation of SomeBody is to use a "provider" or "factory" object, you can inject it in the constructor and invoke it at the right time:
interface SampleService {
fun getSomething(someBody: SomeBody)
}
open class SomeBody(val body: String)
open class UserRequester(
val service: SampleService,
val someBodyProvider: (String) -> SomeBody
) {
fun doGetSomething(someValue: String) {
val response = service.getSomething(someBodyProvider(someValue))
}
}
And mock it in your tests:
val someValue = "foo"
val sampleService: SampleService = mock()
val someBody: SomeBody = mock()
val someBodyProvider: (String) -> SomeBody = mock {
on { invoke(someValue) }.thenReturn(someBody)
}
val userRequester = UserRequester(sampleService, someBodyProvider)
userRequester.doGetSomething("foo")
verify(sampleService).getSomething(someBody)
verify(someBodyProvider).invoke(someValue)
I used an anonymous function but you might as well make it an interface.
I am making a list of observable LiveData objects, that should contain Resource object (https://developer.android.com/topic/libraries/architecture/guide.html#addendum). I don't care what type of data that Resource object is containing.
abstract class LiveResources : LiveData<Resource<Any>>() {
private val mediatorLiveData = MediatorLiveData<Resource<Any>>()
protected val resources = HashSet<LiveData<Resource<Any>>>()
fun addResource(source: LiveData<Resource<Any>>) {
resources.add(source)
mediatorLiveData.addSource(source, resourceObserver)
}
fun removeResource(source: LiveData<Resource<Any>>) {
resources.remove(source)
mediatorLiveData.removeSource(source)
}
private val resourceObserver = Observer<Resource<Any>> {
onSourceChange()
}
abstract fun onSourceChange()
}
Unfortunately when I try to use LiveResources.addResource() with LiveData<Resource<List<String>>> I get TypeMismatch error in my IDE, saying that LiveData<Resource<Any>> was expected.
Your Resource (and/or LiveData) class should be defined with generic covariance in order to make it work. Like so:
class Resource<out T> // <- out marks generic type as covariant
Haven't tried it, but I think this would work
fun <T:Any> addResource(source: LiveData<Resource<T>>)
You should generify the classes to accept Resource<T> i.e LiveData<Resource<T>>. Any is the covariance of any object passed, but I think you are not trying to achieve that.
Another friendly advice is that you don't need to add another abstraction on top of MediatorLiveData that solely does the same you have implemented.
I'm trying to figure out if something is possible. Generally, what I'm trying to do is get a class type of a subclass from within the companion object of the superclass ... In the snipped below, treat the __ as what I need
companion object
{
fun fromSnapshot(snapshot: DataSnapshot): __
{
val model = snapshot.getValue(__)
model.key = snapshot.key
// ...
return model
}
}
Some background ... DataSnapshot is from Firebase, and snapshot.getValue() takes a Class<T>. If I were trying to create an instance of, say, a TestModel, code would be as follows
companion object
{
fun fromSnapshot(snapshot: DataSnapshot): TestModel
{
val model = snapshot.getValue(TestModel::java.class)
model.key = snapshot.key
// ...
return model
}
}
I'm not really sure if what I'm asking is possible in Kotlin. I'm pretty sure it isn't in Java. I hate to mention it, but in Swift, this would be accomplished with what I call "big-S self," or Self, which is the class type of instance self. If you don't know Swift, self is equivalent to Java and Kotlin's this.
Any help would be hugely appreciated!
From your code, it seems to be a very generic function. It doesn't matter what T is and in which companion object this function lives, so I have another version:
inline fun <reified T : FirebaseModel> DataSnapshot.toModelOfType() =
getValue(T::class.java).also { it.key = this.key}
It can be used like this:
someSnapshot.toModelOfType<SomeFirebaseModel>()
instead of your
FirebaseModel.fromSnapshot<SomeFirebaseModel>(someSnapshot)
or with imports
fromSnapshot<SomeFirebaseModel>(someSnapshot)
I prefer mine because it's shorter than your version without imports and more fluent than your version with imports.
I personally suggest Prefer extension functions over Java style utility functions.
Even though I sat on this for days without posting a question, I figured it out less than an hour after posting this question. This can be accomplished with a reified generic type, which allows for usage of the generic type from within a function, however these can only be used as inline functions. Here's my solution
companion object
{
inline fun <reified T : FirebaseModel> fromSnapshot(snapshot: DataSnapshot): T
{
val model = snapshot.getValue(T::class.java)
model.key = snapshot.key
return model
}
}