Android unit tests how to run a method only once - android

I have subclass of Application called myApplication, and i have a method in there to setup crashlytics. But in my unit tests everytime i run a test its restarting a new Application so then it tries to setup crashlytics again each time and then eventually i just get Out of memory exception. How can i force a method to only be executed once since its in Application which is the foundation of the application.
my subclass if necessary , looks like this:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
setupCrashlytics();
}
/**
* sets up Crashlytics.
*/
private void setupCrashlytics() {
Crashlytics.Builder crashlytics= new Crashlytics.Builder();
Fabric.Builder fabric= new Fabric.Builder(this);
fabricBuilder.kits(crashlytics.build());
Fabric.with(fabric.build());
}
}

I assume you're running instrumentation tests, which are dependent of the configuration you apply in your Application subclass.
Although the tests should be run in an environment as close as possible to the production, you can differentiate your test environment by exposing a MyApplication.setIsTestEnvironment() method and using the flag to enable/disable third party dependencies when you're testing.
Alternatively, some testing libraries (i.e. Mockito) allow you to fully or partially mock an object. You can do that to prevent your real Application class from initializing unwanted dependencies when testing.

Related

AWS Device Farm Espresso tests order

I'm starting using Device Farm. My problem is that it is ignoring the order that I used to write tests. In local all works fine because Espresso executes tests in a certain order. To declare that order I used alphabetical order. All my classes starts with a letter (E.g. "A_EspressoTest") so I can choose which class has to be ran first.
Into my classes I use
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
to declare in which order my tests have to be ran.
It seems also like Device Farm ignores all my annotations (E.g. "#Test" ) because it is running also methods that doesn't have that annotation.
Lorenzo,
As of today, there is no way to specify the order of test execution with Espresso. Additionally, your observation about the #Test annotation is correct, we don't currently use that when discovering what test classes/test methods are selected.
AWS Device Farm currently discovers tests based on JUnit 3 style naming conventions (classes starting/ending with the word Test and methods within those classes starting with the word test.
For example:
// This class would be automatically discovered (by name).
public class LoginTests extends InstrumentationTestCase {
// This method would be automatically discovered (by name).
public void testLoginFormUsingInvalidCredentials() {
// ...
}
// This method would **not** be automatically discovered.
#Test
public void loginWithValidCredentials() {
// ...
}
}
// This class would **not** be automatically discovered.
public class Login extends InstrumentationTestCase {
// This method would **not** be automatically discovered since the class was not discovered.
public void testLoginFormWithValidCredentials() {
// ...
}
}
With all that said, we've heard plenty of feedback and requests for supporting test discovery using all JUnit 4 annotations and it's an area of improvement we're definitely taking a look at.
Hope that helps!
Best,
Andrew # AWS Device Farm

Is this a correct way to use Dagger 2 for Android app in unit test to override dependencies with mocks/fakes?

For 'regular' Java project overriding the dependencies in the unit tests with mock/fake ones is easy. You have to simply build your Dagger component and give it to the 'main' class that drives you application.
For Android things are not that simple and I've searched for a long time for decent example but I was unable to find so I had to created my own implementation and I will really appreciate feedback is this a correct way to use Dagger 2 or there is a simpler/more elegant way to override the dependencies.
Here the explanation (project source can be found on github):
Given we have a simple app that uses Dagger 2 with single dagger component with single module we want to create android unit tests that use JUnit4, Mockito and Espresso:
In the MyApp Application class the component/injector is initialized like this:
public class MyApp extends Application {
private MyDaggerComponent mInjector;
public void onCreate() {
super.onCreate();
initInjector();
}
protected void initInjector() {
mInjector = DaggerMyDaggerComponent.builder().httpModule(new HttpModule(new OkHttpClient())).build();
onInjectorInitialized(mInjector);
}
private void onInjectorInitialized(MyDaggerComponent inj) {
inj.inject(this);
}
public void externalInjectorInitialization(MyDaggerComponent injector) {
mInjector = injector;
onInjectorInitialized(injector);
}
...
In the code above:
Normal application start goes trough onCreate() which calls initInjector() which creates the injector and then calls onInjectorInitialized().
The externalInjectorInitialization() method is ment to be called by the unit tests in order to set the injector from external source, i.e. a unit test.
So far, so good.
Let's see how the things on the unit tests side looks:
We need to create MyTestApp calls which extends MyApp class and overrides initInjector with empty method in order to avoid double injector creation (because we will create a new one in our unit test):
public class MyTestApp extends MyApp {
#Override
protected void initInjector() {
// empty
}
}
Then we have to somehow replace the original MyApp with MyTestApp. This is done via custom test runner:
public class MyTestRunner extends AndroidJUnitRunner {
#Override
public Application newApplication(ClassLoader cl,
String className,
Context context) throws InstantiationException,
IllegalAccessException,
ClassNotFoundException {
return super.newApplication(cl, MyTestApp.class.getName(), context);
}
}
... where in newApplication() we effectively replace the original app class with the test one.
Then we have to tell the testing framework that we have and want to use our custom test runner so in the build.gradle we add:
defaultConfig {
...
testInstrumentationRunner 'com.bolyartech.d2overrides.utils.MyTestRunner'
...
}
When a unit test is run our original MyApp is replaced with MyTestApp. Now we have to create and provide our component/injector with mocks/fakes to the app with externalInjectorInitialization(). For that purpose we extends the normal ActivityTestRule:
#Rule
public ActivityTestRule<Act_Main> mActivityRule = new ActivityTestRule<Act_Main>(
Act_Main.class) {
#Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
OkHttpClient mockHttp = create mock OkHttpClient
MyDaggerComponent injector = DaggerMyDaggerComponent.
builder().httpModule(new HttpModule(mockHttp)).build();
MyApp app = (MyApp) InstrumentationRegistry.getInstrumentation().
getTargetContext().getApplicationContext();
app.externalInjectorInitialization(injector);
}
};
and then we do our test the usual way:
#Test
public void testHttpRequest() throws IOException {
onView(withId(R.id.btn_execute)).perform(click());
onView(withId(R.id.tv_result))
.check(matches(withText(EXPECTED_RESPONSE_BODY)));
}
Above method for (module) overrides works but it requires creating one test class per each test in order to be able to provide separate rule/(mocks setup) per each test. I suspect/guess/hope that there is a easier and more elegant way. Is there?
This method is largely based on the answer of #tomrozb for this question. I just added the logic to avoid double injector creation.
1. Inject over dependencies
Two things to note:
Components can provide themselves
If you can inject it once, you can inject it again (and override the old dependencies)
What I do is just inject from my test case over the old dependencies. Since your code is clean and everything is scoped correctly nothing should go wrong—right?
The following will only work if you don't rely on Global State since changing the app component at runtime will not work if you keep references to the old one at some place. As soon as you create your next Activity it will fetch the new app component and your test dependencies will be provided.
This method depends on correct handling of scopes. Finishing and restarting an activity should recreate its dependencies. You therefore can switch app components when there is no activity running or before starting a new one.
In your testcase just create your component as you need it
// in #Test or #Before, just inject 'over' the old state
App app = (App) InstrumentationRegistry.getTargetContext().getApplicationContext();
AppComponent component = DaggerAppComponent.builder()
.appModule(new AppModule(app))
.build();
component.inject(app);
If you have an application like the following...
public class App extends Application {
#Inject
AppComponent mComponent;
#Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this);
}
}
...it will inject itself and any other dependencies you have defined in your Application. Any subsequent call will then get the new dependencies.
2. Use a different configuration & Application
You can chose the configuration to be used with your instrumentation test:
android {
...
testBuildType "staging"
}
Using gradle resource merging this leaves you with the option to use multiple different versions of your App for different build types.
Move your Application class from the main source folder to the debug and release folders. Gradle will compile the right source set depending on the configuration. You then can modify your debug and release version of your app to your needs.
If you do not want to have different Application classes for debug and release, you could make another buildType, used just for your instrumentation tests. The same principle applies: Duplicate the Application class to every source set folder, or you will receive compile errors. Since you would then need to have the same class in the debug and rlease directory, you can make another directory to contain your class used for both debug and release. Then add the directory used to your debug and release source sets.
There is a simpler way to do this, even the Dagger 2 docs mention it but they don't make it very obvious. Here's a snippet from the documentation.
#Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
The Thermosiphon implements Pump and wherever a Pump is requested Dagger injects a Thermosiphon.
Coming back to your example. You can create a Module with a static boolean data member which allows you to dynamically switch between your real and mock test objects, like so.
#Module
public class HttpModule {
private static boolean isMockingHttp;
public HttpModule() {}
public static boolean mockHttp(boolean isMockingHttp) {
HttpModule.isMockingHttp = isMockingHttp;
}
#Provides
HttpClient providesHttpClient(OkHttpClient impl, MockHttpClient mockImpl) {
return HttpModule.isMockingHttp ? mockImpl : impl;
}
}
HttpClient can be the super class which is extended or an interface which is implemented by OkHttpClient and MockHttpClient. Dagger will automatically construct the required class and inject it's internal dependencies just like Thermosiphon.
To mock your HttpClient, just call HttpModule.mockHttp(true) before your dependencies are injected in your application code.
The benefits to this approach are:
No need to create separate test components since the mocks are injected at a module level.
Application code remains pristine.

android espresso login once before running tests

I've been trying to cover my Android app with tests and have started using espresso recently. Pretty impressed with it so far. However most of my app's functionality requires that users are logged in. And since all tests are independent, this requires registering a new user for each test. This works fine however the time required for each test increases considerably because of this.
I am trying to find a way to register a user once in a class (of tests) and then use that same user account to perform all the tests in that class.
One way I have been able to do this is to actually have only one test (#Test) method that runs all the other tests in the order I want. However this is an all or nothing approach, since the gradle cAT task only outputs the results once at the end without providing info about the intermediate tests that may have passed/failed.
I also tried the #BeforeClass approach which however did not work (no gradle output from the class where I had used this even with the debug option and it seemed like it took a long time before it moved on to the next class of tests).
Is there a better approach to register a user once at start of a class and then logout once at the end of testing?
Any help appreciated.
Ideally you would test the login/logout functionality in a set of tests that just test different login/logout scenarios, and let the other tests focus on other use cases. However, since the other scenarios depend on the user being logged in, it sounds like one way to solve this would be to provide a mock version of the app component handling the login. For the other login dependent tests, you would inject this mock at the start and it would return mock user credentials that the rest of the app can work with.
Here's an example where Dagger, Mockito and Espresso is being used to accomplish this: https://engineering.circle.com/instrumentation-testing-with-dagger-mockito-and-espresso-f07b5f62a85b
I test an app that requires this same scenario. The easiest way I've gotten around this is to split up logging in and out into their own test classes. Then you add all your test classes to a suite, starting and ending with the login and logout suites respectively. Your test suites ends up looking kind of like this.
#RunWith(Suite.class)
#Suite.SuiteClasses({
LoginSetup.class,
SmokeTests.class,
LogoutTearDown.class
})
EDIT: Here is an example of both the LoginSetup and LogoutTearDown tests. This solution really should only be for end-to-end tests and comprise a small portion of your testing efforts. fejd provides a solution for a full testing stack which also needs to be considered.
#LargeTest
public class SmokeSetup extends LogInTestFixture {
#Rule
public ActivityTestRule<LoginActivity> mLoginActivity = new ActivityTestRule<>(LoginActivity.class);
#Test
public void testSetup() throws IOException {
onView(withId(R.id.username_field)).perform(replaceText("username"));
onView(withId(R.id.password_field)).perform(replaceText("password"));
onView(withId(R.id.login_button)).perform(click());
}
}
#LargeTest
public class LogoutTearDown extends LogInTestFixture {
#Rule
public ActivityTestRule<MainActivity> mMainActivity = new ActivityTestRule<>(MainActivity.class);
#Test
public void testLogout() throws IOException {
onView(withId(R.id.toolbar_menu)).perform(click());
onView(withId(R.id.logout_button)).perform(click());
}
}
The approach with logging in with #Before is nice but if your login is slow, your combined test time will be very slow.
Here's a great hack that works. The strategy is simple: run every test in order and fail every test before they get a chance to run if a certain test fails (in this case login test).
#RunWith(AndroidJUnit4.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
#LargeTest
public class YourTestsThatDependsOnLogin {
private static failEverything;
#Before
public void beforeTest() {
// Fail every test before it has a chance to run if login failed
if (failEverything) {
Assert.fail("Login failed so every test should fail");
}
}
#Test
public void test0_REQUIREDTEST_login() {
failEverything = true;
// Your code for login
// Your login method must fail the test if it fails.
login();
failEverything = false; // We are safe to continue.
}
// test1 test2 test3 etc...
}
Pros:
What you asked for works and it is fast (if your login is slow)
You can have multiple tests that depend on different logins, meaning you can do a bunch of tests for user1, then a bunch of tests for user2 etc.
Quick to set up.
Cons:
Not standard procedure and someone might wonder why so many tests
fail...
You should mock users instead of actually logging in. You should test your login separately and tests should not depend on each other.
Add the following function in your test file, replace the code in the try
block with yours that performs the login actions.
#Before
fun setUp() {
// Login if it is on the LoginActivity
try {
// Type email and password
Espresso.onView(ViewMatchers.withId(R.id.et_email))
.perform(ViewActions.typeText("a_test_account_username"), ViewActions.closeSoftKeyboard())
Espresso.onView(ViewMatchers.withId(R.id.et_password))
.perform(ViewActions.typeText("a_test_account_password"), ViewActions.closeSoftKeyboard())
// Click login button
Espresso.onView(ViewMatchers.withId(R.id.btn_login)).perform(ViewActions.click())
} catch (e: NoMatchingViewException) {
//view not displayed logic
}
}
With this #Before annotation, this setUp function will be executed before any other tests you have in this test file. If the app lands on Login Activity, do the login in this setUp function. The example here assumes there is an EditText for email and password, as well as a login button. It uses Expresso to type the email and password, then hit the login button. The try catch block is to ensure if you are not landing on the Login Activity, it will catch the error and do nothing, and if you didn't land on the Login Activity, then you are good to go on other tests anyway.
Note: this is Kotlin code, but it looks very similar to Java.
My Application also requires the user to be logged in through-out the test run.
However, I am able to login the first time and the application remembers my username/password throughout the test run. In fact, it remembers the credentials until I force it to forget them or uninstall and install the app again.
During a test run, after every test, my app goes to the background and is resumed again at the beginning of the next test. I am guessing your application requires a user to enter their credentials every time you bring it to the front from the background (banking application maybe?). Is there a setting in your application that will "Remember your credentials"? If yes, you can easily enable it right after you login for the first time in your test run.
Other than that, I think you should talk to the developers about providing you a way to remember your credentials.
If you are using #BeforeClass in Kotlin, you need to place it inside companion object. That way the block under #BeforeClass will be executed just once before the 1st test runs in the class. Also have #AfterClass inside companion object, so that it runs at the very end of the last test of the class.
Keep in mind that once the compiler moves from inside companion object to outside of it, the context of the app under test is lost. You can get back the context by launching the main activity(activity after login) of your app.
companion object {
#ClassRule
#JvmField
val activity = ActivityTestRule(Activity::class.java)
#BeforeClass
#JvmStatic
fun setUp() {
// login block
}
#AfterClass
#JvmStatic
fun tearDown() {
// logout block
}
}
#Test
fun sampleTest() {
activity.launchActivity(Intent())
}

Android espresso and account picker

I am having trouble making the instrumentation test using the Espresso.
I have an activity where account picker is popup-ed when app is started (main activity).
If customer clicks on cancel (in dialog), picker is popup up again; If user clicks on add, the result is picked up on activity result.
I dont know how to create a simple test with espresso which will include that picker.
When I create the Instrumentation test with the MainActivity, I got this message:
No activities in stage RESUMED...
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity>{
MainActivity myActivity;
public MainActivityTest(){
super(MainActivity.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
getActivity();
}
public void testAccountPicker(){
onView(withText("Choose an account")).check(matches(isDisplayed()));
}
}
Does anybody had similar problem?
Thanx for your answers in advance.
That's a tough one :). The problem here is that once the flow leaves your application (Google Account Picker is an external application), Espresso ends the test. Account Picker is an activity from the package com.google.android.gms, thus external. Once it's started, your test is finished and you'll never be able to match anything in the dialog.
You have three possible solutions to make your tests feasible:
Using classpath substitution on your app to fake the intents; or
Fixing your app "testability"; or
Using dependency injection, like Dagger
I'll show how to use classpath substitution. The technique is really simple: you should isolate your Intent creation in a separate class, say IntentsFactory and, during tests, override that class.
Say your factory is in com.yourapp.factories.IntentsFactory and it is something like this:
public class IntentsFactory {
public static Intent getAccountPickerIntent (Context context) {
return AccountPicker.newChooseAccountIntent(null, null, new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null);
}
}
You should create in your test app (say it is com.yourapp.tests) a package with the same name and methods, but that returns a different Intent, a mocked/dummy one:
public class IntentsFactory {
public static Intent getAccountPickerIntent (Context context) {
return new Intent(context, MyDummyAccountPickerActivity.class);
}
}
Whenever your tests execute, they will use the "nearest" class in the classpath, that is, the IntentsFactory from your tests. Instead of returning an intent that send the flow to another app, the flow will go to an class of your project and Espresso won't end the tests.
The only caveat here is that you'll have to create the MyDummyAccountPickerActivity which will return a result and a Bundle similar to the one returned by the framework class. The activity should exists in your app's manifest and you'll have to instruct your emulator Dalvik runtime to allow classpath (check it out this this and this links) substitution with the following command line:
adb shell setprop dalvik.vm.dexopt-flags v=n,o=v
adb shell stop installd
adb shell start installd
And execute your tests.
I had a similar problem to test the Camera and it's thoroughly discussed in Espresso forum
Seems, that you must operate on a root view which in your case the "account picker". Try this out:
public void testAccountPicker(){
onView(withText("Choose an account"))
.inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
.check(matches(isDisplayed()));
}
There are several ways that you might be able to go about testing this using Espresso Intents https://google.github.io/android-testing-support-library/docs/espresso/intents/
You can verify that the Intent was sent to open the account picker by using the intended() syntax. You can also verify the behavior of your activity with the returned result from the picker using the intending().respondWith() syntax.
If you really want to interact with the picker directly, you may be able to using the UIAutomator API: https://developer.android.com/topic/libraries/testing-support-library/index.html#UIAutomator
UIAutomator can be used inside of Espresso tests.

reusing junit tests in Android

I have a library whose Junit 3 tests I'd like to reuse on Android. The problem is that these tests depend on files being present (test data).
For Android this means that some setup code needs to call getContext().getAssets() and dump the asset files to the sdcard, where they will be picked up by the tests.
The problem is: where do I put this setup code? I can't put it in the test's setup, as it inherits from TestCase (and anyway that would mean its no longer portable). I can't put it in the AllTests suite, as that inherits from TestSuite. Since its a Junit project there's no Activity whose onCreate I can hook.
The solution is to make a custom InstrumentationTestRunner. Override onCreate, where custom setup logic can be stored. For example:
public class InstrumentationTestRunner extends
android.test.InstrumentationTestRunner {
#Override
public void onCreate(Bundle arguments) {
setUpTests();
super.onCreate(arguments);
}
protected void setUpTests() {
// ....
}
}

Categories

Resources