I'm starting to use Kotlin on a little demo android app. I've created a sharedpreferences helper class which i'm trying to test with Junit and Mockito. Below is my sharedprefshelper:
public class SharedPrefsHelperImp( cont : Context) : SharedPrefsHelper {
val prefsname: String = "prefs"
var prefs: SharedPreferences? = null
var edit: SharedPreferences.Editor? = null
init {
prefs = cont.getSharedPreferences(prefsname, Context.MODE_PRIVATE)
edit = prefs!!.edit()
}
override fun getPrefsStringValue(key: String) : String {
return prefs!!.getString(key, "")
}
override fun addPrefsStringVal( key : String, value: String) {
edit!!.putString(key, value).commit()
}
override fun getSharedPrefsBool(key : String): Boolean {
return prefs!!.getBoolean(key, false)
}
override fun addSharedPrefsBool(key : String, value : Boolean) {
edit!!.putBoolean(key, value).commit()
}
}
here is my test class:
class SharedPrefsHelperImpTest {
#Mock var cont : Context? = null
#Mock var mockprefs : SharedPreferences? = null
#Mock var mockprefsedit : SharedPreferences.Editor? = null
var prefshelper : SharedPrefsHelper? = null
#Before
fun setUp() {
//MockitoAnnotations.initMocks(this)
cont = Mockito.mock(Context::class.java)
mockprefs = Mockito.mock(SharedPreferences::class.java)
mockprefsedit = Mockito.mock(SharedPreferences.Editor::class.java)
`when`(cont!!.getSharedPreferences(anyString(), anyInt())).thenReturn(mockprefs!!)
`when`(mockprefs!!.edit()).thenReturn(mockprefsedit!!)
prefshelper = SharedPrefsHelperImp(cont!!)
}
#Test
fun testNotNull(){
Assert.assertNotNull(cont)
Assert.assertNotNull(mockprefs)
Assert.assertNotNull(mockprefsedit)
}
#Test
fun testItemAdded()
{
prefshelper!!.addPrefsStringVal("thing", "thing")
verify(mockprefsedit)!!.putString(anyString(), anyString())
}
#Test
fun testGetString()
{
prefshelper!!.getPrefsStringValue("key")
verify(mockprefs)!!.getString("key", "")
}
}
Issue is when I call addPrefsValueString() in the helper. the line
edit!!.putString(key, value).commit()
throws a null pointer exception? not sure why? I've setup the mock sharedprefs and sharedpreferences.Edit in the test class method annotated with #Before (shown below)
#Before
fun setUp() {
//MockitoAnnotations.initMocks(this)
cont = Mockito.mock(Context::class.java)
mockprefs = Mockito.mock(SharedPreferences::class.java)
mockprefsedit = Mockito.mock(SharedPreferences.Editor::class.java)
`when`(cont!!.getSharedPreferences(anyString(), anyInt())).thenReturn(mockprefs!!)
`when`(mockprefs!!.edit()).thenReturn(mockprefsedit!!)
prefshelper = SharedPrefsHelperImp(cont!!)
}
i'm sure my code is less than optimal.
EDIT:
Here's my fix for the testItemAdded() method. need to return the mock preferences editor on the first call.
#Test
fun testItemAdded()
{
`when`(mockprefsedit?.putString(anyString(), anyString())).thenReturn(mockprefsedit)
`when`(mockprefsedit?.commit()).thenReturn(true)
prefshelper!!.addPrefsStringVal("thing", "thing")
verify(mockprefsedit)!!.putString(anyString(), anyString())
verify(mockprefsedit)!!.commit()
}
You should set expectations for the call below, on your mock object (mockprefsedit). As well for the object returned, on which commit is invoked.
edit!!.putString(key, value)
thanks
Sriram
Related
I am writing Junit test for shared preferences but facing below issue
Method
public void storeUser(User user) {
final SharedPreferences userPrefs = context.getSharedPreferences(USER_PREFS, Context.MODE_PRIVATE);
userPrefs.edit().putString(USER_ID_KEY, user.getUserID()).apply();
}
Junit Class
class UserRepoTest {
private lateinit var context: Context
private lateinit var userRepo: UserRepo
private lateinit var userPrefs: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
#Before
fun setup() {
context = mock()
userRepo = UserRepo(context)
userPrefs = mock()
editor = mock()
}
#Test
fun `check mocked instances are not null`() {
context assertNotEquals null
userRepo assertNotEquals null
}
#Test
fun `when store user then load user`() {
whenever(context.getSharedPreferences(USER_PREFS, Context.MODE_PRIVATE)).thenReturn(userPrefs)
whenever(userPrefs.edit()).thenReturn(editor)
val user = getUser()
verify(editor, times(1)).putString(USER_ID_KEY, user!!.userID).apply()
// verify(userPrefs.edit(), times(1)).putString(USER_ID_KEY, user!!.userID).apply()
userRepo.storeUser(user)
}
private fun getUser(): User? {
return User().apply {
userID = "test#yopmail.com"
}
}
companion object {
const val USER_PREFS = "userprefs"
const val USER_ID_KEY = "UserID";
}
}
but getting below error not sure why its saying not invoked
Wanted but not invoked:
editor.putString(
"UserID",
"test#yopmail.com"
);
-> at com.example.UserRepoTest.when store user then load user(UserRepoTest.kt:41)
Actually, there were zero interactions with this mock.
Order is important. Call the action storeUser() before you try to verify what it did.
I'm writing unit test for my ViewModel. I have mocked my data source and want to test that datasource returns success and error cases. If i run tests individually everything is OK.
In the first method i mocked to return success, in the second method i mocked to return error. When i run these 2 tests together (by clicking run tests in class name), in the second method i want dataSource.getPackageCard() to return ResponseState.Error("error1337") however it returns ResponseState.Success(responseDto). In other words, it remembers the mocked value from 1st method. Why ? How to solve that problem ?
#MediumTest
#RunWith(AndroidJUnit4::class)
#ExperimentalCoroutinesApi
class MyViewModelTest {
#get: Rule
var instantExecutorRule = InstantTaskExecutorRule()
#get: Rule
var mainCoroutineRule = MainCoroutineRule()
private lateinit var viewModel: MyViewModel
lateinit var MyRepository: MyRepository
val responseDto = MyResponseDto().apply {
val myList = mutableListOf<CardListGroupDTO>()
myList.add(CardListGroupDTO(cardGroupType = "test",
headerTitle = "test",
buttonAll = ButtonDto(title = "test", url = "test")
))
groupList = myList
}
#MockK
lateinit var dataSource: MyDataSource
#Before
fun setup() {
MockKAnnotations.init(this)
MyRepository = MyRepositoryImpl.getInstance(dataSource)
viewModel = MyViewModel(MyRepository)
}
#After
fun afterTests() {
unmockkAll()
unmockkObject(dataSource)
}
#Test
fun `test successful case`() = runBlockingTest {
// given
coEvery {
dataSource.getPackageCard()
} returns ResponseState.Success(
responseDto
)
var counter = 0
viewModel.MyResponseDto.observeForever(
object : Observer<ResponseState<MyResponseDto>> {
override fun onChanged(t: ResponseState<MyResponseDto>) {
// println(viewModel.MyResponseDto.value)
when (counter) {
0 ->
Truth.assertThat(t).isEqualTo(ResponseState.Loading(true))
1 ->
Truth.assertThat(t).isEqualTo(ResponseState.Success(responseDto))
2 -> {
Truth.assertThat(t).isEqualTo(ResponseState.Loading(false))
viewModel.MyResponseDto.removeObserver(this)
}
}
counter++
}
})
viewModel.getPackageCard()
}
#Test
fun `test error case`() = runBlockingTest {
val errorMessage = "error1337"
// given
coEvery {
dataSource.getPackageCard()
} returns ResponseState.Error(
errorMessage
)
var counter = 0
viewModel.MyResponseDto.observeForever(
object : Observer<ResponseState<MyResponseDto>> {
override fun onChanged(t: ResponseState<MyResponseDto>) {
// println(viewModel.MyResponseDto.value)
when (counter) {
0 ->
Truth.assertThat(t).isEqualTo(ResponseState.Loading(true))
1 ->
Truth.assertThat(t).isEqualTo(ResponseState.Error(errorMessage))
2 -> {
Truth.assertThat(t).isEqualTo(ResponseState.Loading(false))
viewModel.MyResponseDto.removeObserver(this)
}
}
counter++
}
})
viewModel.getPackageCard()
}
}
I found the answer finally. Since i use static repository MyRepositoryImpl.getInstance(dataSource), the datasource is mocked once. Second mock is not valid. I did manual singleton, inside companioan object create if it is not null, if nonnull return the object. This is the cause of my problem.
I solved the problem, by removing the singleton pattern i implemented as the above. I used constructor injection and made my repository singleton in this way. In my unit tests my repository is not singleton any more.
#Singleton
class MyRepositoryImpl #Inject constructor(
private val myRemoteDataSource: MyRemoteDataSource
) : MyRepository
And my viewmodel test is fixed when i write the following :
#Before
fun setup() {
MockKAnnotations.init(this)
myRepository = MyRepositoryImpl(dataSource)
viewModel = MyViewModel(myRepository)
}
I'm new on unit testing. I'm trying to do unit testing on my view model class but my test fail with error:
Wanted but not invoked:
toggleMovieFavorite.invoke(
Movie(id=1, title=Title, overview=Overview, releaseDate=01/01/2025, posterPath=, backdropPath=, originalLanguage=ES, originalTitle=Title, popularity=5.0, voteAverage=7.0, favorite=false)
);
-> at xyz.jonthn.usescases.ToggleMovieFavorite.invoke(ToggleMovieFavorite.kt:7)
Actually, there were zero interactions with this mock.
This is my test file
#RunWith(MockitoJUnitRunner::class)
class DetailViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#Mock
lateinit var findMovieById: FindMovieById
#Mock
lateinit var toggleMovieFavorite: ToggleMovieFavorite
#Mock
lateinit var observer: Observer<Movie>
private lateinit var vm: DetailViewModel
#ExperimentalCoroutinesApi
#Before
fun setUp() {
Dispatchers.setMain(Dispatchers.Unconfined)
vm = DetailViewModel(1, findMovieById, toggleMovieFavorite, Dispatchers.Unconfined)
}
#ExperimentalCoroutinesApi
#After
fun tearDown() {
Dispatchers.resetMain()
}
#Test
fun `when favorite clicked, the toggleMovieFavorite use case is invoked`() {
runBlocking {
val movie = mockedMovie.copy(id = 1)
whenever(findMovieById.invoke(1)).thenReturn(movie)
whenever(toggleMovieFavorite.invoke(movie)).thenReturn(movie.copy(favorite = !movie.favorite))
vm.movie.observeForever(observer)
vm.onFavoriteClicked()
verify(toggleMovieFavorite).invoke(movie)
}
}
val mockedMovie = Movie(
0,
"Title",
"Overview",
"01/01/2025",
"",
"",
"ES",
"Title",
5.0,
7.0,
false)
}
This is my DetailViewModel:
class DetailViewModel(
private val movieId: Int, private val findMovieById: FindMovieById,
private val toggleMovieFavorite: ToggleMovieFavorite,
uiDispatcher: CoroutineDispatcher) : ScopedViewModel(uiDispatcher) {
private val _movie = MutableLiveData<Movie>()
val movie: LiveData<Movie> get() = _movie
init {
launch {
_movie.value = findMovieById.invoke(movieId)
}
}
fun onFavoriteClicked() {
launch {
movie.value?.let {
_movie.value = toggleMovieFavorite.invoke(it)
}
}
}
}
And my use case ToggleMovieFavorite:
class ToggleMovieFavorite(private val moviesRepository: MoviesRepository) {
suspend fun invoke(movie: Movie): Movie = with(movie) {
copy(favorite = !favorite).also { moviesRepository.update(it) }
}
}
Thank you so much for your help guys!!!
i thougt mockito does not invoke your init method on viewmodel, you should put your declaration of vm on each #test instead of #Before since method findMovieById called at init, right before the function is mocked.
I'm new in testing and have a problem with a mock object. When I pass the call method that doesn't require any value the test is successful. But when I pass the value to the method and put it to JsonObject I have NPE. Why passed the argument to JsonObject lead to an error?
Class I want to test:
open class UserRepositoryImpl #Inject constructor(
private val movieApi: MovieApi
) : UserRepository {
override suspend fun createSession(requestToken: String): String {
val body = JsonObject().apply {
addProperty("request_token", requestToken) // error happen when I put argument to JsonObject
}
return movieApi.createSession(body = body) // NPE
.await()
.body()
?.getAsJsonPrimitive("session_id")
?.asString ?: ""
}
}
Test case:
class UserRepositoryImplTest {
#get:Rule
val mockitoRule: MockitoRule = MockitoJUnit.rule()
#Mock
lateinit var movieApi: MovieApi
#Mock
lateinit var localPrefStorage: LocalPrefStorage
lateinit var userRepository: UserRepositoryImpl
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
userRepository = UserRepositoryImpl(movieApi, localPrefStorage)
}
#Test
fun createSession() {
runBlocking {
val value = userRepository.createSession("request_token")
assertEquals(value, "")
}
}
}
You are mocking the movieApi and you should tell the mocked movieApi what to do if createSession method called
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
userRepository = UserRepositoryImpl(movieApi, localPrefStorage)
when(movieApi.createSession(body)).thenReturn(result)
}
In my application i am following MVP pattern, in this i want to make junit test cases for presenter and model(interactor) classes to validate the business logic.
Below is the code i have written for presenter and model, and i also also written a junit test case as mentioned below.
LoginPresenterImpl.kt
class LoginPresenterImpl : LoginPresenter, LoginResponseCallback {
lateinit var loginIntegractor:LoginIntegractor
override fun loginSuccess(user: User) {
loginView.hidProgress()
loginView.loginSuucces(user)
}
override fun loginFailed(errorMessage: String) {
loginView.hidProgress()
loginView.loginFailed(errorMessage)
}
lateinit var loginView:LoginView
constructor(context: Context,loginView: LoginView){
this.loginView = loginView;
loginIntegractor = LoginInteractorImpl(context,this);
}
override fun login(userName: String, password: String) {
loginView.showProgress();
loginIntegractor.login(userName,password);
}
}
LoginInteractorImpl.kt This file consist of business logic of login
class LoginInteractorImpl : LoginIntegractor {
val TAG:String = LoginInteractorImpl::class.java.simpleName;
var loginResponseCallback: LoginResponseCallback;
var context: Context? = null;
constructor(context: Context,loginResponseCallback: LoginResponseCallback){
this.context = context;
this.loginResponseCallback = loginResponseCallback;
}
constructor(loginResponseCallback: LoginResponseCallback){
this.loginResponseCallback = loginResponseCallback;
}
override fun login(username: String, password: String) {
if(username.trim().isBlank()){
loginResponseCallback.loginFailed("Please enter username");
}
else if(password.trim().isBlank()){
loginResponseCallback.loginFailed("Please enter password");
}
else{
val apiService:ApiService = ApiService.Factory.create();
val jsonObject = JSONObject();
jsonObject.put("username",username);
jsonObject.put("password",password);
val call:Call<LoginResponse> = apiService.login(jsonObject.toString())
call.enqueue(object : Callback<LoginResponse> {
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
Log.d(TAG, "login success")
if (response != null) {
val status = response.body()!!.getStatus()
if (status == 0) {
loginResponseCallback.loginSuccess(response.body()!!.getUser())
} else {
loginResponseCallback.loginFailed(response.body()!!.getMessage())
}
}
}
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
Log.d(TAG, "login failed")
loginResponseCallback.loginFailed("Something went wrong while login")
}
})
}
}
}
LoginInteractorTest.kt This is juit test case file.
class LoginInteractorTest {
var loginIntegractor:LoginIntegractor? = null
#Mock
private lateinit var callback: LoginResponseCallback
#Captor
private lateinit var argumentCaptor:ArgumentCaptor<LoginResponseCallback>;
#Captor
private lateinit var nameCapcture:ArgumentCaptor<String>;
#Captor
private lateinit var pwdcapcture:ArgumentCaptor<String>;
private lateinit var user: User;
#Before
fun setUp(){
callback = mock()
user = mock()
val captor = argumentCaptor<() -> Unit>()
nameCapcture = ArgumentCaptor.forClass(String::class.java)
pwdcapcture = ArgumentCaptor.forClass(String::class.java)
loginIntegractor = LoginInteractorImpl(callback)
}
#Test
fun testLogin() {
MockitoAnnotations.initMocks(this)
loginIntegractor?.login("ashok","narra")
// verify(loginIntegractor?.login(nameCapcture.capture(),pwdcapcture.capture()))
// argumentCaptor.value.loginSuccess(ArgumentMatchers.any(User::class.java))
Mockito.verify(callback).loginSuccess(ArgumentMatchers.any(User::class.java));
}
}
test case fails saying java.lang.IllegalStateException: ArgumentMatchers.any(User::class.java) must not be null". Can anyone suggest how to we implement junit test cases for presenter/model classes in android using kotlin?
I had the same issue recently and to be honest I couldn't solve it and ended up using mockito-kotlin.
Instead of
Mockito.verify(callback).loginSuccess(ArgumentMatchers.any(User::class.java));
You can simply write:
Mockito.verify(callback).loginSuccess(any())
Where any() comes from mockito kotlin. There's usually no need to specify the type, but in case you need it you can always do it like any<User>()