Issue
I am trying to write a unit test that tests if the exception is thrown, but I am not entirely sure how to do that.
Here is the code:
class GpsInfo {
private lateinit var locationManager : LocationManager
fun getGpsInfo(activity: Activity) : Location? {
locationManager = activity.getSystemService(Context.LOCATION_SERVICE) as LocationManager
var gpsCoordinates: Location
try {
gpsCoordinates = getGpsCoordinatesFromGpsProvider(locationManager)
} catch(e: UnableAccessGPSProvider) {
gpsCoordinates = getGpsCoordinatesFromNetwork(locationManager)
} catch(e: Exception) {
throw UnableGetGpsCoordinates("Cant get GPS coordinates")
}
return gpsCoordinates
}
private fun getGpsCoordinatesFromGpsProvider(locationManager: LocationManager) : Location {
val gpsCoordinates = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
if (gpsCoordinates == null) {
throw UnableAccessGPSProvider("Cant get GPS coordinates from GPS Provider")
}
return gpsCoordinates
}
private fun getGpsCoordinatesFromNetwork(locationManager: LocationManager) : Location {
val gpsCoordinates = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
if (gpsCoordinates == null) {
throw UnableAccessNetwork("Cant get GPS coordinates from network")
}
return gpsCoordinates
}
And here my sad unit testing attempt, I am trying to achieve it with a mockking class:
import android.app.Activity
import io.mockk.every
import io.mockk.mockk
import org.junit.*
import kotlin.test.assertFailsWith
class GpsServiceUnitTest {
private val gps = mockk<GpsInfo>()
@Before
fun iniz() {
every { gps.getGpsInfo(Activity()) } throws UnableAccessGPSProvider("No GPS found")
}
@Test
fun testFails() {
assertFailsWith(UnableAccessGPSProvider::class) {
gps.getGpsInfo(Activity())
}
}
}
How can I achieve this? I need to feed the function something so it would throw the exception, but I don't see how this would work.
Thanks!
Solution
Please avoid mocking a class you're trying to test, since you want to see the "real" behaviour.
My first step would be to slightly change your class and move field locationManager
to constructor:
class GpsInfo(private val locationManager : LocationManager) {
fun getGpsInfo(): Location? {
var gpsCoordinates: Location
try {
gpsCoordinates = getGpsCoordinatesFromGpsProvider(locationManager)
} catch (e: UnableAccessGPSProvider) {
gpsCoordinates = getGpsCoordinatesFromNetwork(locationManager)
} catch (e: Exception) {
throw UnableGetGpsCoordinates("Cant get GPS coordinates")
}
return gpsCoordinates
}
private fun getGpsCoordinatesFromGpsProvider(locationManager: LocationManager): Location {
val gpsCoordinates = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
if (gpsCoordinates == null) {
throw UnableAccessGPSProvider("Cant get GPS coordinates from GPS Provider")
}
return gpsCoordinates
}
private fun getGpsCoordinatesFromNetwork(locationManager: LocationManager): Location {
val gpsCoordinates = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
if (gpsCoordinates == null) {
throw UnableAccessNetwork("Cant get GPS coordinates from network")
}
return gpsCoordinates
}
}
Now you can mock the field easily to make it throw an exception:
import com.nhaarman.mockito_kotlin.doThrow
import com.nhaarman.mockito_kotlin.mock
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.catchThrowable
@Test
fun `should throw UnableGetGpsCoordinates exception`() {
val locationManagerMock : LocationManager = mock {
on { getLastKnownLocation(any()) } doThrow UnableAccessGPSProvider()
}
val throwable = catchThrowable { GpsInfo(locationManagerMock).getGpsInfo() }
assertThat(throwable).isNotNull()
}
You could also leave locationManager
as method parameter and mock it for the test, but it would require more effort to test it then, because we would have to mock two times - the activity and the .getSystemService(Context.LOCATION_SERVICE)
call.
BTW: I recommend using nhaarman library for mocking and assertJ for asserting
Answered By - Neo
Answer Checked By - Terry (JavaFixing Volunteer)