Adding Test Module for RoboGuice When Using Robolectric 2 - android

I am currently upgrading robolectric from version 1 to 2. In my current version I use the following to provide the test module (for binding) to roboguice.
public class RoboTestRunner extends RobolectricTestRunner {
public RoboTestRunner(Class<?> testClass) throws
InitializationError {
super(testClass);
}
#Override
public void prepareTest(Object test) {
Application app = Robolectric.application;
RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE,
Modules.override(RoboGuice.newDefaultRoboModule(app)).with(new
TestModule()));
Injector injector = RoboGuice.getInjector(app);
injector.injectMembers(test);
}
}
However now I have upgraded the prepareTest method is not in this class. Where should I run this code in the new version?
UPDATE
I have found the way to do this. I need to create an class which extends android.app.Application in the project and reference this in the Manifest. Then I create a class like so
public class TestApplication extends Application implements TestLifecycleApplication {
#Override
public void onCreate() {
super.onCreate();
RoboGuice.setBaseApplicationInjector(this, RoboGuice.DEFAULT_STAGE,
RoboGuice.newDefaultRoboModule(this), new TestModule());
}
#Override
public void beforeTest(Method method) {}
#Override
public void prepareTest(Object test) {
TestApplication application = (TestApplication) Robolectric.application;
RoboGuice.setBaseApplicationInjector(application, RoboGuice.DEFAULT_STAGE,
RoboGuice.newDefaultRoboModule(application), new TestModule());
RoboGuice.getInjector(application).injectMembers(test);
}
#Override
public void afterTest(Method method) {}
}
As this class has Test at the start robolectric should automatically find it and use it. However this doesn't seem to be happening. Does anybody know why?
UPDATE 2
This blog would suggest that the testmodule needs to be in the same package however I have all tests in a different package. How do I work around this?

Your TestApplication class should extend your own Application class, not android.app.Application, and it should be in the same package as your Application.
... however I have all tests in a different package.
That shouldn't be a problem. Put your TestApplication in your test module, but use the package from Application.
e.g., if you're using maven, the files would live here:
src/main/java/com/example/Application.java
src/test/java/com/example/TestApplication.java

Related

Android: Gradle build task adds a final keyword to a method

I'm experiencing something strange, that never happened to me before.
This is simple class that extends an Application class:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
}
}
It is a library module, than I try to extend it from an application module:
public class MyApp extends MyApplication {
#Override
public void onCreate() {
super.onCreate();
Log.i("MyApp", "Application created");
}
}
Now when compiling with minifyEnabled=true I'm getting strange result:
In library module class get changed to (note the final keyword):
public class MyApplication extends Application {
MyApplication() {
}
public final void onCreate() {
super.onCreate();
}
}
And this causes compilation error, since MyApp cannot override a final method.
Did someone faced this issue?
Thanks in advance.
Happens only with minify enabled. Class MyApplication is added to proguard-rules as exception.
Happens only on Gradle > 7.1.3. Reverting to 7.1.3 fixes the cause.

How do I provide a custom Application class to my Espresso Activity test?

I'm pretty new to Espresso, but I am trying to test a relatively simple Activity. My android app has its own custom Application class. How can I tell Espresso to use a mocked (or custom) version of this class?
Here is my custom version of the Application. It creates some test data (edited here for brevity). Down the road, I will also be overriding some of the methods.
public class MockMyApplication extends MyApplication {
#Override
public void onCreate() {
super.onCreate();
// create some location data for testing
DataRecord rec = new DataRecord(1);
rec.setName("TestLoc1");
rec.setDescription("an important customer");
MyData.add(rec);
}
}
My attempt to test using this, looks like this:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class LocEditActivityTest extends AndroidJUnitRunner {
#Rule
public ActivityTestRule<LocEditActivity> activityTestRule
= new ActivityTestRule<>(LocEditActivity.class);
#Override
public Application newApplication(ClassLoader cl, String className, Context context) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
return super.newApplication(cl, MockMyApplication.class.getName(), context);
}
#Test
public void testActivity_ExistingLoc() {
Intent i = new Intent();
i.putExtra("loc",1);
activityTestRule.launchActivity(i);
onView(withId(R.id.editName)).check(matches(withText("TestLoc1")));
// shutdown
onView(withContentDescription("Navigate up")).perform(click());
}
}
Using a debugger, I have determined that when LocEditAcitivity's onCreate calls getApplication(), it returns a MyApplication class with empty data, and not the MockedMyApplication with my test data.
Found it!
Looks like I misunderstood the "Runner" class usage. I needed to create my own Runner that extended AndroidJUnitRunner:
import android.app.Application;
import android.content.Context;
import android.support.test.runner.AndroidJUnitRunner;
// Our own test runniner - uses MockMyApplication as a mocked app class
public class MyAndroidTestRunner extends AndroidJUnitRunner {
#Override
public Application newApplication(ClassLoader cl, String className, Context context) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
return super.newApplication(cl, MockMyApplication.class.getName(), context);
}
}
And then in build.gradle (app), the testInstrumentationRunner entry needs to point to the new runner:
testInstrumentationRunner "com.winwaed.xyzapp.MyAndroidTestRunner"
As the newApplication override was in the wrong place, this should be removed from my test class. Also, the test class no longer extends any classes. (ie. I essentially split the runner and test classes - as I said, I misunderstood the runner class)

Robolectric 3 with Fabric Crashlytics

I am trying to ShadowClass Crashlytics/Fabric so that Robotlectric 3 tests do not fail. What I have so far is this:
The custom test runner that adds the Shadow class for Fabric:
public class TestRunner extends RobolectricGradleTestRunner {
public TestRunner(Class<?> klass) throws InitializationError {
super(klass);
}
#Override
protected ShadowMap createShadowMap() {
return super.createShadowMap()
.newBuilder().addShadowClass(ShadowFabric.class).build();
}
#Override
public InstrumentationConfiguration createClassLoaderConfig() {
InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder();
builder.addInstrumentedClass(ShadowFabric.class.getName());
return builder.build();
}
}
The shadow class for Fabric:
#Implements(Fabric.class)
public class ShadowFabric {
#Implementation
public static Fabric with(Context context, Kit... kits) {
System.out.println("Shadowing Fabric");
return null;
}
}
My application class for my app:
public class MyApp extends Application {
#Override
public void onCreate() {
setupCrashlytics();
}
protected void setupCrashlytics() {
Crashlytics crashlyticsKit = new Crashlytics.Builder().core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()).build();
// Initialize Fabric with the debug-disabled crashlytics.
Fabric.with(this, crashlyticsKit);
}
}
And here is the test that passes in Debug (because Crashlytics is disabled on it), but fails in release mode because the ShadowClass is not working correctly:
#RunWith(TestRunner.class)
#Config(constants = BuildConfig.class, sdk=21, packageName="com.my.release.package.name", shadows={ShadowFabric.class})
public class MyTest {
#Test
public void testGreenDAOsave() {
// blah
}
}
The error I get with Crashlytics / Fabric during the test is the following:
STANDARD_ERROR
io.fabric.sdk.android.services.concurrency.UnmetDependencyException: com.crashlytics.android.core.CrashlyticsMissingDependencyException:
This app relies on Crashlytics. Please sign up for access at https://fabric.io/sign_up
install an Android build tool and ask a team member to invite you to this app's organization.
The stack trace shows that MyApp.setupCrashlytics() is being called and Fabric.with() is failing.
1) YES, the app is registered with Crashlytics.
2) YES, I did contact Crashlytics support email. I was told 'Robolectric is not supported'.
From what I can see, I just need to get the shadow class thing working and then Crashlytics will get shadowed and not init'd.
Ideas / Help would be very much appreciated!
This is my usual advice how to write a test against something not testable.
Extract you Fabric initialisation to protected method:
public class <MyApplicationName> {
public void onCreate() {
initFabric();
}
#VisibileForTesting
void initFabric() {
....
}
}
Create Test<MayApplicationName> class in test sources (same package and override Fabric initialisation:
public class Test<MyApplicationName> {
void initFabric() {
//nothing to do
}
}
Everywhere where you need using Fabric use DI (Dependency Injection) to mock Fabric in tests. Even more, I would suggest you create Analytics/Crash/Distribution class and hide Fabric usage from entire application.
And final you have left classes that wrap/hide the Fabric. Here you can write a custom shadow, spy on the real object or leave it untested. And you already tried to write custom shadow without success, also, spying is not an option here.
Happy coding!

Android Unit Testing: Cucumber-jvm + Android Instrumentation

Using: Cucumber-JVM with Android Instrumentation + Espresso).
Reference Github link: https://github.com/mfellner/cucumber-android for this. The simple sample works fine.
Problem with cucumber-jvm + android instrumentation:
But in the sample in link, it uses ActivityInstrumentationTestCase2 which is deprecated. I would like to use #Rule - ActivityTestRule class as said by Google.
Here my question is:
For using cucumber-jvm, I am using the CucumberInstrumentationCore instead of
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner".
So Android junit annotations like #Rule for ActivityTestRule is not parsed by CucumberInstrumentation. So Is it possible to overcome this problem?
Then is my decision to use cucumber-jvm + android instrumentation has to be reverted back. My question is not only for the deprecated class but globally is it good idea to go for cucumber-jvm + android instrumentation, as it can't use instrumentation features because of annotation parsing.
Your runner should inherit from Android JUnitRunner:
public class Instrumentation extends AndroidJUnitRunner {
private final CucumberInstrumentationCore instrumentationCore = new CucumberInstrumentationCore(this);
#Override
public void onCreate(final Bundle bundle) {
instrumentationCore.create(bundle);
super.onCreate(bundle);
}
#Override
public void onStart() {
waitForIdleSync();
instrumentationCore.start();
}
Pay attention to the super class been initialized at the end of onCreate.
Then, edit your defaultConfig in your build.grade file:
defaultConfig {
applicationId "your.package.name"
testApplicationId "your.steps.package"
testInstrumentationRunner "your.package.Instrumentation"
}
And finally, the steps definition class, which inherited from ActivityInstrumentationTestCase2 should look like:
public class BaseStepDefinitions {
public static final String TAG = BaseStepDefinitions.class.getSimpleName();
#Rule
public ActivityTestRule<StartupActivity> mActivityRule = new ActivityTestRule<>(StartupActivity.class);
#Before
public void setUp() throws Exception {
mActivityRule.launchActivity(null);
mActivityRule.getActivity();
}
/**
* All the clean up of application's data and state after each scenario must happen here
*/
#After
public void tearDown() throws Exception {
}
#When("^I login with \"([^\"]*)\" and \"([^\"]*)\"$")
public void i_login_with_and(String user, String password) throws Throwable {
// Login...
}
The setUp function runs before each scenario, and launching the activity.
Globally, if it serves your needs I don't see any problem using it like so, both Cucumber annotations and the JUnit annotations can be parsed in this way.
I've created a sample project: github.com/Clutcha/EspressoCucumber

Android - ActivityUnitTestCase - Tests Always Pass

I am using Android Studio to try and test my activity. Here is the basic code:
public class MyActivityTest extends ActivityUnitTestCase<MyActivity> {
public MyActivityTest() {
super(MyActivity.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
}
#SmallTest
public void testSomething() {
Assert.assertNotNull("something is null", null);
}
}
I would expect that this test case fails. Everything I try passes though. This seems like a strange question, but how can I make my test case fail? What am I doing wrong?
I managed to get this working, sort of. I found this on a bug report:
We are in the process of deprecating ActivityUnitTestCase. We recommend to move business logic to a separate class and unit test it with gradle unit test support (mockable android.jar).
So I extended ActivityInstrumentationTestCase2 instead and ran the test as an Instrumentation Test rather than a Unit Test. That worked. Here is basically what I have now:
public class MyActivityTest extends ActivityInstrumentationTestCase2<MyActivity> {
public MyActivityTest() {
super(MyActivity.class);
}
public void testSomething() throws Exception {
//test goes here
Assert.assertEquals(message, expectedObject, actualObject);
}
}
I'm still not sure why I was seeing the behavior I was earlier, but at least I can test now. Here is a screenshot of my Test Build Configuration:

Categories

Resources