Unit test Android, getString from resource - android

I am trying to do an unit test for an android app and I need to get a string from res.string resources. The class that I want to test is a POJO class. I am doing the app in two languages, due to this, I need to get a string from resource. The problem is that I cannot get the context or the activity, is possible? I know that with Instrumentation test I can do it, but I need to test some functions (white box test) before to do the instrumentation test (black box test).
This is the function that I have to test:
public void setDiaByText(String textView) {
getll_diaSeleccionado().clear();
if (textView.contains(context.getResources().getString(R.string.sInicialLunes))) {
getll_diaSeleccionado().add(0);
getIsSelectedArray()[0] = true;
getI_idiaSeleccionado()[0] =1;
} else
{
getIsSelectedArray()[0] = false;
getI_idiaSeleccionado()[0] =0;
}
}
And this is the test:
#Test
public void setDiaByTextView() {
String texto = "L,M,X,J,V,S,D";
alertaPOJO.setDiaByText(texto);
assertEquals(alertaPOJO.getIsSelectedArray()[0], true);
assertEquals(alertaPOJO.getI_idiaSeleccionado()[0], 1);
}
It crash when try to do context.getResources().getString(R.string.sInicialLunes))
If I put 'Mon' instead of context.getResources().getString(R.string.sInicialLunes)) or 'L' it work perfectly so, is possible to get the context or the activity in order to access to resource folder?
I am testing with Mockito and the setUp function is:
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = Mockito.mock(Alerta.class);
Mockito.when(mContext.getApplicationContext()).thenReturn(mContext);
alertaPOJO = new AlertaPOJO();
}
Thanks

If you are using Context only for obtaining String resource, I would go by mocking only getResources().getString() part like this (see JUnit4 notation):
#RunWith(MockitoJUnitRunner.class)
public class AlertaPOJOTest {
#Mock
Context mMockContext;
#Test
public void setDiaByTextView() {
String texto = "L,M,X,J,V,S,D";
when(mMockContext.getString(R.string.sInicialLunes))
.thenReturn(INITIAL_LUNES);
alertaPOJO.setDiaByText(texto);
assertEquals(alertaPOJO.getIsSelectedArray()[0], true);
assertEquals(alertaPOJO.getI_idiaSeleccionado()[0], 1);
}
}
There are many reasons to stay with JVM tests, most important one, they are running quicker.

Untested: would it work to use the below, and probably targetContext?
android {
testOptions {
unitTests {
includeAndroidResources = true
}
}
}

You don't have a real android Context while you are using JVM unit test. For your case, maybe you can try Android Instrumentation Test, typically it is implemented in the "androidTest" directory of your project.

If you use MockK it's the same.
#RunWith(AndroidJUnit4::class)
class YourClassUnitTest : TestCase() {
#MockK
private lateinit var resources: Resources
#Before
public override fun setUp() {
MockKAnnotations.init(this)
}
#Test
fun test() {
every {
resources.getQuantityString(R.plurals.age, YEARS, YEARS)
} returns AGE
every {
resources.getString(
R.string.surname,
SURNAME
)
} returns TITLE
// Assume you test this method that returns data class
// (fields are calculated with getQuantityString and getString)
val data = getData(YEARS, SURNAME)
assertEquals(AGE, data.age)
assertEquals(TITLE, data.title)
}
companion object {
const val YEARS = 10
const val AGE = "$YEARS years"
const val SURNAME = "Johns"
const val TITLE = "Mr. $SURNAME"
}
}
See also Skip a parameter in MockK unit test, Kotlin to get a result of string resources for any data.

Related

mockk not working while executing entire android test package

I have written test cases for my view model. Which when I run individually or when I run the Test class. They get executed successfully. But when I run the complete androidTest package, I get this Exception
io.mockk.MockKException
Here is the code that runs successfully in isolation.
#RunWith(AndroidJUnit4::class)
class MyViewModelTest{
#Test
fun test_one(){
getInstrumentation().runOnMainSync(Runnable {
val context = ApplicationProvider.getApplicationContext<Context>()
mockkStatic(MyManager::class)
val myInterface = mockk<MyInterface>()
every { MyManager.getCommunicator() } returns myInterface
every { myInterface.context } returns context
every { myInterface.getLongFromGTM(any()) } returns 0
val viewModel = MyViewModel(context as Application)
viewModel.model = MyDataModel()
viewModel.model.isRepeatEligible = true
val res = viewModel.isRepeatEligible()
Truth.assertThat(res).isTrue()
})
}
}
This is the error I am getting while running entire androidTest package:
Here are the detailed used classes
1 .) MyManager.java
public class MyManager {
private static MyInterface myCommunicator;
public static MyInterface getCommunicator() {
if (myCommunicator == null) {
synchronized (MyManager.class) {
if (myCommunicator == null) {
Class<?> cls = Class.forName("mypackage.communicator.MyCommunicator");
myCommunicator = (MyInterface) cls.newInstance();
}
}
}
return myCommunicator;
}
}
2.) MyViewModel.kt
class MyViewModel(application: Application) : BaseViewModel(application) {
var model = MyDataModel()
private val timerDelay: Long by lazy {
myCommunicator.getLongFromGTM("key_p2m_timer_delay")
}
val timerDuration: Long by lazy {
myCommunicator.getLongFromGTM("key_p2m_timer_duration")
}
fun isRepeatEligible(): Boolean {
model.apply {
return isRepeatEligible && !isLinkBased && !isAlreadyPresent
}
}
Mocking something with MockK is not contrained to just one function. Specifically, when you mock an object with mockkStatic, the object will from then on be a mock until it is unmocked using unmockkStatic or unmockkAll.
In your case, I guess the problem arises due to the static mocking of MyManager that lets subsequent tests fail, because they do not expect the object to be mocked.
This could be solved with an "after" function (e.g. using JUnit4, a function annotated with #After) that calls unmockAll.
Alternatively, if you want to make sure that the object is only mocked locally, you can use a variant of mockkStatic that accepts a block that is the only place where the object is mocked like this:
mockkStatic(MyManager::class) {
// inside this block, MyManager is mocked
}
// MyManager is automatically unmocked after the block
Update
As mentioned in your comment, you do not call MyManager.getCommunicator() directly in MyViewModel, but via an extension property
val myCommunicator : MyInterface = MyManager.getCommunicator()
This may cause your test setup to be still in place after your test, even when you unmock MyManager, because the property myCommunicator will keep its value - the mocked interface.
This can be solved by changing your property to not be initialized with the value of MyManager.getCommunicator(), but instead you should define a getter that calls MyManager.getCommunicator():
val myCommunicator: MyInterface get() = MyManager.getCommunicator()
This way, you do always get the current value of MyManager.getCommunicator() and not the value that was set once on initialization.
See https://kotlinlang.org/docs/properties.html#getters-and-setters for details on property getters.

How to mock a class of a library used by the app during instrumented test?

I am working on an Android library and I am writing an instrumented test for it using UI Automator and Mockk.
The library has a class called InstallManager which will install stuff on the device. I want the InstallManager to throw an exception so that I can test if an error notification will be shown.
All I do is finding the Update (Install) button and click on it
val updateButtonComponent = device.findObject(By.text(updateButtonText))
updateButtonComponent.click()
How can I mock/manipulate the InstallManager which is being used by the library during the automated test?
What I tried:
I tried mocking the InstallManager before running the automated test, hoping that UI Automator would magically know that it should use this mocked InstallManager. But (as I thought already) it does not work like that...
private fun breakInstallManager() {
installManager = spyk(InstallManager(mockk(relaxed = true), nonExistentFile))
every { installManager.getString(any()) } returns ""
every { installManager.packageName } returns ""
}
For mocking InstallManager class in android tests, call MockKAnnotations.init method. Then in the test method you can specify the return value
#RelaxedMockK
private lateinit var installManager: InstallManager
#Before
fun setUp() {
MockKAnnotations.init(this)
// …
}
#Test
fun installManagerTest(){
every { installManager.getString(any()) } returns ""
every { installManager.packageName } returns ""
// …
}

Unit testing a RecyclerView Adapter throws NullPointerException when accessing mObservables

My view model holds a very simple recyclerview adapter
When I try to send it messages (which in turn calls notifyDatasetChanged) it throws an exception like so
java.lang.NullPointerException
at androidx.recyclerview.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:11996)
at
the problem is that the mObservers variable from AdapterDataObservable is null
the thing is that this extends Observable<AdapterDataObserver> which in turn defines mObservers as
protected final ArrayList<T> mObservers = new ArrayList<T>();
so basically the moment my adapter is instantiated , it will call
private final AdapterDataObservable mObservable = new AdapterDataObservable();
(which is called by the way, mObservable is not null)
which in turn should call mObservers = new ArrayList<T>();
can someone explain why this is never called? or if there is a way to get past this problem?
as a side note the adapter is not mocked it is a solid object.
Edit:
Here is the code of the tests I'm using:
class LoginViewModelTest {
private lateinit var vm: LoginViewModel
#get:Rule
val rule = InstantTaskExecutorRule()
#Before
fun setUp() {
whenever(settings.hasShownWelcome).thenReturn(false)
whenever(settings.serverIp).thenReturn("http://127.0.0.1")
//this is where the crash happens
vm = LoginViewModel(settings, service, app, TestLog, TestDispatchers) { p -> permissionGranted }
}
And below is the code that is tested:
class LoginViewModel(private val settings: ISettings, private val service: AppService, application: Application, l: ILog, dispatchers: IDispatchers, val permissionChecker: (String) -> Boolean) : BaseViewModel(application, l, dispatchers)
val stepAdapter :StepAdapter
init {
val maxSteps = calculateSteps()
//after this assignment, during the normal run, the stepAdapter.mObservable.mObservers is an empty array
//during unit tests, after this assignment it is null
stepAdapter = StepAdapter(maxSteps)
}
I fixed mine by spying on the adapter and stubbing notifyDataSetChanged.
val spyAdapter = spyk(adapter)
every { spyAdapter.notifyDataSetChanged() } returns Unit
spyAdapter.changeItems(items)
verify { spyAdapter.notifyDataSetChanged() }
Please, note that changeItems calls notifyDataSetChanged internally.
i don't know if you have already found a solution or not, but this is for other people like me who ran into a similar problem:
make the test an android test (aka instrumented test) and not a unit test.
while i cannot fully explain why, it seems that when notifying an adapter about a change (notifyItemChanged(), notifyDataSetChanged() etc.) something about the inner logic of android requires an actual RecyclerView/adapter to receive the message.
once i moved my test from the Test folder to the AndroidTest folder, the problem was fixed.
P.S.
make sure to remove your old build configuration! android studio keeps referring to the old one (in the Test folder) and if you don't remove it you will receive a classNotFound error
I solved my problem doing this:
#RunWith(RobolectricTestRunner::class)
#Config(sdk = [Build.VERSION_CODES.N, Build.VERSION_CODES.O], application = AppTest::class)
class HomeAdapterTest {
Where the AppTest is am empty class.
in my case, I use #RunWith(MockitoJUnitRunner.class) instrad of #RunWith(JUnit4.class) and work fine for me... the sample code :
Inside viewModel.fetchPokemonList() function I use adapter.notifyDatasetChanged()
#RunWith(MockitoJUnitRunner.class)
public class PokemonCardListTest {
#Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
#Mock Context context;
#Mock LifecycleOwner lifecycleOwner;
#Mock public List<PokemonCard> pokemonCardList;
private Repository repository;
private Lifecycle lifecycle;
private PokemonCardListViewModel viewModel;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
pokemonCardList = Arrays.asList(
new PokemonCard("1" ,"Card1" , "Artist1"),
new PokemonCard("2" ,"Card2" , "Artist2")
);
repository = new FakeRepository(pokemonCardList);
lifecycle = new LifecycleRegistry(lifecycleOwner);
viewModel = new PokemonCardListViewModel(context , repository);
}
#Test
public void testSearch() {
viewModel.fetchPokemonList("99");
assertEquals(viewModel.getAdapter().getValue().getItemCount() , pokemonCardList.size());
viewModel.fetchPokemonList("0");
assertEquals(viewModel.getAdapter().getValue().getItemCount() , 0);
}

Mockito when-thenReturn with SharedPreference value

I'm doing my first deep dive into unit testing with Mockito, so please bear with me. I'm working on this test:
class PasswordStateManagerTest {
private lateinit var passwordStateManager: PasswordStateManager
#MockK
private lateinit var mockContext: Context
#MockK
private lateinit var mockSharedPreferences: SharedPreferences
#Before
fun setup() {
MockKAnnotations.init(this, true)
every{ mockContext.getApplicationSharedPreferences() } returns mockSharedPreferences
// this is the line that won't compile
Mockito.when(mockSharedPreferences.getBoolean("save_password", false)
)
.thenReturn(true)
passwordStateManager = PasswordStateManager(mockSharedPreferences)
}
}
The when.thenReturn line won't compile. It is expecting an open bracket { character where I am trying to execute on .thenReturn. As I read the docs, there is no place for an open bracket in this statement, so I must be off the rails.
Here is the part of the init method of the class being tested, which is what creates the need for the when-thenReturn line in the test:
init {
willSavePassword = prefs.getBoolean("save_password", false)
}
Thanks for any help (and patience while I get up to speed!).
This is because when is a reserved keyword in Kotlin, so the compiler is interpreting this as the beginning of a when statement. For example:
when (value) {
"value1" -> // do thing
}
To fix this, you can either escape the method name with backticks:
Mockito.`when`(mockSharedPreferences.getBoolean("save_password", false)).thenReturn(true);
Or, since you're using MockK anyway, just switch to another every:
every { mockSharedPreferences.getBoolean("save_password", false) } returns true

Mockito and callback returning "Argument(s) are different!"

I'm trying to use mockito on android. I want to use it with some callback.
Here my test :
public class LoginPresenterTest {
private User mUser = new User();
#Mock
private UsersRepository mUsersRepository;
#Mock
private LoginContract.View mLoginView;
/**
* {#link ArgumentCaptor} is a powerful Mockito API to capture argument values and use them to
* perform further actions or assertions on them.
*/
#Captor
private ArgumentCaptor<LoginUserCallback> mLoadLoginUserCallbackCaptor;
private LoginPresenter mLoginPresenter;
#Before
public void setupNotesPresenter() {
// Mockito has a very convenient way to inject mocks by using the #Mock annotation. To
// inject the mocks in the test the initMocks method needs to be called.
MockitoAnnotations.initMocks(this);
// Get a reference to the class under test
mLoginPresenter = new LoginPresenter(mUsersRepository, mLoginView);
// fixtures
mUser.setFirstName("Von");
mUser.setLastName("Miller");
mUser.setUsername("von.miller#broncos.us");
mUser.setPassword("Broncos50superBowlWinners");
}
#Test
public void onLoginFail_ShowFail() {
// When try to login
mLoginPresenter.login("von.miller#broncos.us", "notGoodPassword");
// Callback is captured and invoked with stubbed user
verify(mUsersRepository).login(eq(new User()), mLoadLoginUserCallbackCaptor.capture());
mLoadLoginUserCallbackCaptor.getValue().onLoginComplete(eq(mUser));
// The login progress is show
verify(mLoginView).showLoginFailed(anyString());
}
But I got this error :
Argument(s) are different! Wanted:
mUsersRepository.login(
ch.example.project.Model.User#a45f686,
<Capturing argument>
);
-> at example.ch.project.Login.LoginPresenterTest.onLoginFail_ShowFail(LoginPresenterTest.java:94)
Actual invocation has different arguments:
mUsersRepository.login(
ch.example.project.Model.User#773bdcae,
ch.example.project.Login.LoginPresenter$1#1844b009
);
Maybe the issue is that the second actual argument is ch.example.project.Login.LoginPresenter$1#1844b009 ?
I followed : https://codelabs.developers.google.com/codelabs/android-testing/#5
Thank you for help =)
Edit
The method I try to test (LoginPresenter):
#Override
public void login(String email, String password) {
mLoginView.showLoginInProgress();
User user = new User();
user.setUsername(email);
user.setPassword(password);
mUsersRepository.login(user, new UsersRepository.LoginUserCallback() {
#Override
public void onLoginComplete(User loggedUser) {
mLoginView.showLoginComplete();
}
#Override
public void onErrorAtAttempt(String message) {
mLoginView.showLoginFailed(message);
}
});
}
eq(new User())
When using eq (or not using matchers at all), Mockito compares arguments using the equals method of the instance passed in. Unless you've defined a flexible equals implementation for your User object, this is very likely to fail.
Consider using isA(User.class), which will simply verify that the object instanceof User, or any() or anyObject() to skip matching the first parameter entirely.
I am using mvp pattern with rxjava 2 and dagger 2, and was stuck on unit testing a presenter using Mockito. The code that gave me the "Argument(s) are different!” Error:
#Mock
ImageService imageService;
#Mock
MetadataResponse metadataResponse;
private String imageId = "123456789";
#Test
public void getImageMetadata() {
when(imageService.getImageMetadata(imageId)).thenReturn(Observable.just(Response.success(metadataResponse)));
presenter.getImageMetaData(imageId);
verify(view).showImageData(new ImageData()));
}
Which throws error messages such as the following:
Argument(s) are different! Wanted: Actual invocation has different
arguments: com.example.model.ImageData#5q3v861
Thanks to the answer from #Jeff Bowman, it worked after I changed this line
verify(view).showImageData(new ImageData()));
with
verify(view).showImageData(isA(ImageData.class));

Categories

Resources