how to create unit test cases for kotlin data classes - android

I have created few data classes for my sample application. I need to write unit test cases for those data classes now. I am using Junit4.12. Here is my data class:
data class Tracking(val param1 : String?=null, val param2 : String?=null, val param3 : String?=null){}
I tried writing one basic unit test case for this model like below:
#Test
fun tracking()
{
val trackingData= Tracking("7030054",
"skdfksbfbkjsdf",
"dfkhsbfsjf")
Assert.assertEquals("true",trackingData.param1,"7030054")
}
But i don't see it is right way of performing a unit test case. My Objective for unit test case is to assert an exception if user sends null value as an input. Please help me out.

Your values are all String?, so nullable. If you want them to always be non-null, use String instead. Then you don't need a unit-test since non-nullability will be checked by the compiler.
If they can sometimes be null but not always, you have to first write a function that checks that condition.
A very simple example would be something like
fun simpleValidation() {
if(param1 == null) {
throw RuntimeException("Should not be null")
}
}
in the class tracking.
You can than unit test this by
#Test
fun tracking()
{
val trackingData= Tracking(null,
"skdfksbfbkjsdf",
"dfkhsbfsjf")
Assertions.assertThrows(RuntimeException::class.java) { trackingData.simpleValidation() }
}
assuming you are using JUnit5

Related

Kotlin : implenting an immutable class through the data class method but making sure the input values are clean

I'm new to coding in kotlin and want to implement an immutable class that represents a project with various fields inside.
The easiest way to do this is by using a data class and using the copy() method so that anytime one of the app user modifies a field it results in the backend in a call to the copy method with the modified field producing the new project.
My problem is that this way does not allow for prior checking of parameters (eg : limit string size of the owner, making sure the number of people added to the project is reasonable etc).
If this was java, I'd use a builder pattern but this seems to defeat the purpose of kotlin, and i've read articles that are positive to using builders in kotlin (https://www.baeldung.com/kotlin/builder-pattern)
and others that are completely against (https://code-held.com/2021/01/23/dont-use-builder-in-kotlin/).
I haven't found any way to "modify" the copy method and to add the parameter sanitization checks that are needed for each parameter. I would appreciate any "smooth" idea to implement this, if anybody has found it. The goal would also be to throw exeptions/sealed classes variables so that the app UI can tell the user what went wrong instead of a generic error message just mentioning that the project was not modified.
I agree with the second link. If you look at the comments on the Baeldung article, you'll see even they were convinced and pledged to revise the article.
You can throw exceptions in an init block but if these are exceptions that are not caused by programmer error, it would be more Kotlin-idiomatic to expose a single constructor-like function that returns a wrapper or just null for invalid input.
Examples:
data class Person(val name: String, val age: Int = 0) {
init {
if (age < 0) {
throw IllegalArgumentException("Age $age is less than 0.")
}
}
}
If you want to return a wrapper or nullable, a data class isn't suitable for preventing invalid input because the generated copy() function will always return a fully constructed object. Sadly, Kotlin does not support overriding the generated copy() function.
sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val reason: String): Result<T>()
class Person private constructor(val name: String, val age: Int = 0) {
companion object {
fun build(name: String, age: Int = 0): Result<Person> {
return when {
age < 0 -> Failure("Age $age is less than 0.")
else -> Success(Person(name, age))
}
}
}
fun buildCopy(name: String = this.name, age: Int = this.age) = build(name, age)
}

test case using mockk in not working for function

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)

JUnit test not sending values as parameters to function (Kotlin)

I am creating a simple junit test to test a function in my view model but the first assertion fails as the function I call returns null. When I debug the function I call has null parameters which is weird cause I pass them in.
I have spent time debugging and searching for why I am having that issue but I have found nothing that fixes my issue or tells me what the issue is.
#RunWith(MockitoJUnitRunner::class)
class CurrencyUnitTest {
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
#Mock
val currencyViewModel : CurrencyViewModel = mock(CurrencyViewModel::class.java)
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val rates: HashMap<String, Double> =
hashMapOf(
"USD" to 1.323234,
"GBP" to 2.392394,
"AUD" to 0.328429,
"KWR" to 893.4833
)
val currencyRates = MutableLiveData<Resource<CurrencyRatesData?>>()
val resource = Resource<CurrencyRatesData?>(Status.SUCCESS, CurrencyRatesData("CAD", rates, 0))
currencyRates.value = resource
`when`(currencyViewModel.currencyRatesData).thenReturn(currencyRates)
val baseCurrency = MutableLiveData<String>()
baseCurrency.value = "CAD"
`when`(currencyViewModel.baseCurrency).thenReturn(baseCurrency)
}
#Test
fun calculateValueTest() {
// this fails
assertEquals("0.36", currencyViewModel.calculateValue("AUD", "1.11"))
}
}
Mocked classes will not really be called. If you want to test your currencyViewModel.calculateValue() method, create a real object of that class and mock possible constructor arguments.
To add to what Ben has said: the class you want to test has to be a real object, not a mock. A mock "does nothing" per default, and only does what you do it to tell you, so to test it does not make any sense.
What you mock is the dependencies of the class you test, i.e. the objects you pass to its' constructor.
In short: if you want to test CurrencyViewModel, create an object of it instead of mocking it.

How to unit test retrofit call?

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.

Android - mocking issue

I have a custom class:
class MyClass {
var name = ""
fun changeName(newName: String) {
name = newName
}
}
and my testing class:
#Test
fun testVerifyMock() {
val instance: MyClass = mock()
instance.changeName("newname")
Assert.assertEquals("newname", instance.name)
}
I'm faily new to Unit Tests and I'm kinda stuck, can someone please point me to why I get this error:
java.lang.AssertionError:
Expected :newname
Actual :null
Basically the call instance.changeName("newname") doesn't seem to be changing the name since it's always null
Mockito mocks just ignore what you pass to their methods unless you explicitly tell them what to do. In the case of changeName, the parameter is just ignored and therefore the name will remain null. I don't see why you would use a mock here anyway, so just change to:
val instance = MyClass()
...
Here's a post on "when to use mock".

Categories

Resources