nullPointerException on sharedpreferences helper class When running a test - android

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

Wanted but not invoked: Actually, there were zero interactions with this mock. for sharedpreferences

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.

Mocked multiple times, but mocked value does not change after secondly mocked in different test method

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)
}

Android Unit Test ViewModel Wanted but not invoked

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.

Android mock object show NPE on when new object created inside method

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)
}

Making junit test case for interactor/presenter in android(kotlin) using mockito

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>()

Categories

Resources