reusing junit tests in Android - 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() {
// ....
}
}

Related

How can I mock and test this class?

The follows was the code which I want to test.
public class Demo {
private static final List<Pair<String, String>> mList;
static {
mList = new ArrayList<>();
mList.add(new Pair<>("F0", "T1"));
mList.add(new Pair<>("F1", "T2"));
mList.add(new Pair<>("F2", "T3"));
}
public String getStr(int pos) {
return mList.get(pos).first;
}
}
I was an android developer. I have get some trouble in test and mock the code.I have use mockito.
I have try some code to test it,but the result was not my expect.
1.First try
#Test
public void test(){
Demo demo=new Demo();
assertEquals(demo.getStr(0),"F0");
/**
* java.lang.AssertionError:
* Expected :null
* Actual :F0
*/
}
2.Second try
#Test
public void test() {
Demo demo = mock(Demo.class);
doCallRealMethod().when(demo).getStr(0);
assertEquals(demo.getStr(0), "F0");
/**
* java.lang.AssertionError:
* Expected :null
* Actual :F0
*/
}
Anyone tell me how can I resolve this problem to make demo.getStr(0) == "F0" by call the real method? Thanks!
===========================
Another question relate to it
I have try an another test to test android.util.Pair class, and the result is that "pair.first" was null,.(There are androidTest and test directory,I put it into test package.Did it impact the result?)
import android.util.Pair;
import org.junit.Test;
import org.mockito.Mockito;
import static org.junit.Assert.assertEquals;
public class DemoTest {
#Test
public void test1(){
Pair<String,String> pair=new Pair("First","Second");
assertEquals("First",pair.first);
//pair.first was null,why?
}
#Test
public void test2(){
Pair<String,String> pair= Mockito.spy(Pair.class);
assertEquals("First",pair.first);
//pair.first was null also,why?
}
}
Why the simple code is correct in real android environment,but failure in test?
I had the same problem too. month ago I have problem with TextUtils class too.
I report this to jUnit but they told me the problem is with android package because in unit test environment you don't have access to platform specific classes
for that pair case you can use this package. this works for me
import android.support.v4.util.Pair;
The problem in your first try is, that the public field "first" is actually null.
Is the Pair class the one from the "javafx.util" package or a custom implementation?
Did you forget "this.first = first" or something similar in the constructor of the "Pair" class?
I would also recommend to change the following line:
assertEquals(demo.getStr(0),"F0");
to
assertEquals("F0", demo.getStr(0));
so that the error is printed correctly.
Your second try does not make any sense. What is the point in mocking the class you want to test?
I think the second example has the same problem as the first one. Pair.first is never set. If you fix that, it should also work (untested).
From Google's Android tools website:
"Method ... not mocked."
The android.jar file that is used to run unit tests does not contain any actual code - that is provided by the Android system image on real devices. Instead, all methods throw exceptions (by default). This is to make sure your unit tests only test your code and do not depend on any particular behaviour of the Android platform (that you have not explicitly mocked e.g. using Mockito).
So how can we solve this?
In other words. If you need a default android class to work properly you either have to include it from a separate repository, or implement it yourself.
In the case of Android's Pair class. You can use android.support.v4.util.Pair instead.
To get access to this class, you can include com.android.support:support-compat:27.0.0 in your build.gradle or dependencies file.
If you are not using Gradle, you can copy the implementation of this file and use it in place of the official one. Or you can try and download the .jar file from this older version https://mvnrepository.com/artifact/com.google.android/support-v4/r7 (I have not tested whether it works)
Another approach (based on this) is to create the class in app/src/test/java/android/util/Pair.java and copy the code from the Android implementation.
This way you don't need extra dependencies. (There may be issues related to the implementation changing after you make the copy, but the dependencies may become stale as well.)

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 unit tests how to run a method only once

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.

Possible to identify in setup which JUnit test case is being executed?

I have writing an Android JUnit test for an activity in my application. The Activity is modal, and can be configured via the launching intent.
I would like to write test methods to test the different modes. This would involve a setActivityIntent call in the setup method, configuring the Activity based on the test case we are running.
My question is, how can I determine which test case is about to be run from the setup method?
It turns out the answer to this question is very easy. junit.framework.TestCase has a getName method that returns the name of the current test case. Perfect.
In Junit 4.X, you can do the samething with TestName class:
public class MyTest {
#Rule public TestName name = new TestName();
#Before
public void before() {
System.out.println("running...", name.getMethodName());
}
}

Categories

Resources