Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fastest way to create a mock Activity for testing

I'm currently unit-testing a library. In certain classes, I need to pass an activity as parameter in some static methods. The library itself contains no activities. I need to somehow obtain an instance of a mock activity to use in each individual method test.

I've already read the Activity Testing Tutorial and the Testing Fundamentals section. Most of what it is said there only makes sense if you are about to test Activities already existing in the project to be tested. But I just need a mock one to do things like showing dialogs and run short tasks in the Ui thread.

What would the fastest, simplest way to achieve this? Should I create the mock activity in my test project and also provide xml layout resources for the dummy UI?


UPDATE
Since I've not found any way of creating a mock activity automatically I decided to provide it by myself. I've created inside the test project a dummy activity doing nothing and provided a dummy layout via xml. Then I coded my test extending ActivityInstrumentationTestCase2:

    public class LibraryTest extends ActivityInstrumentationTestCase2<MockActivity> {

        public LibraryTest(String name) {
            super(MockActivity.class);
        }

        protected void setUp() throws Exception {
            super.setUp();
        }

        public void testAMethodFromLibrary() {
            fail("Not yet implemented");
        }
    }

Where MockActivity is the aforementioned mock activity I've created in this test project. However, seems that the Android test framework is having trouble launching the activity and it comes up with this exception:

        java.lang.RuntimeException: Exception during suite construction
        at android.test.suitebuilder.TestSuiteBuilder$FailedToCreateTests.testSuiteConstructionFailed(TestSuiteBuilder.java:239)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
        at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
        at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:520)
        at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1447)
        Caused by: java.lang.NullPointerException: Method name must not be null.
        at java.lang.ClassCache.findMethodByName(ClassCache.java:297)
        at java.lang.Class.getMethod(Class.java:985)
        at android.test.suitebuilder.TestMethod.getAnnotation(TestMethod.java:60)
        at android.test.suitebuilder.annotation.HasMethodAnnotation.apply(HasMethodAnnotation.java:39)
        at android.test.suitebuilder.annotation.HasMethodAnnotation.apply(HasMethodAnnotation.java:30)
        at com.android.internal.util.Predicates$OrPredicate.apply(Predicates.java:106)
        at android.test.suitebuilder.annotation.HasAnnotation.apply(HasAnnotation.java:42)
        at android.test.suitebuilder.annotation.HasAnnotation.apply(HasAnnotation.java:31)
        at com.android.internal.util.Predicates$NotPredicate.apply(Predicates.java:122)
        at android.test.suitebuilder.TestSuiteBuilder.satisfiesAllPredicates(TestSuiteBuilder.java:254)
        at android.test.suitebuilder.TestSuiteBuilder.build(TestSuiteBuilder.java:190)
        at android.test.InstrumentationTestRunner.onCreate(InstrumentationTestRunner.java:373)
        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4218)
        at android.app.ActivityThread.access$3000(ActivityThread.java:125)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2071)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:123)
        at android.app.ActivityThread.main(ActivityThread.java:4627)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
        at dalvik.system.NativeStart.main(Native Method)

Now I'm totally lost here. How can this be so complicated? Have I chosen the correct way? I just want to launch a dialog in a test method. Maybe the framework is messing up because the activity to test is not in the target project?

Any help here would be much appreciated. I'm getting out of time and if I can't find the proper way to do this I'd have to create a second project using my library, move the mock activity there and test from the test project. This is a lot of code because I'd have to include a method in a (now not generic) mock activity just to call every library method I want to test.

like image 386
Mister Smith Avatar asked Oct 23 '12 16:10

Mister Smith


People also ask

Is mocking good practice?

Mocks can produce better tests by reducing a factor that could cause the test to fail (e.g. needing to access an external resource like a database, which could be offline for reasons unrelated to your test) and helping you isolate the classes being tested.

How do you unit test an activity?

To test an activity, you use the ActivityTestRule class provided by the Android Testing Support Library. This rule provides functional testing of a single activity. The activity under test will be launched before each test annotated with @Test and before any method annotated with @Before.

What is the mocking method?

Mocking is done when you invoke methods of a class that has external communication like database calls or rest calls. Through mocking you can explicitly define the return value of methods without actually executing the steps of the method.

What is a mock for testing?

Mocking is a process used in unit testing when the unit being tested has external dependencies. The purpose of mocking is to isolate and focus on the code being tested and not on the behavior or state of external dependencies.


2 Answers

With androidX libraries, you can use ActivityScenario. Simply import androidTestImplementation("androidx.test:core:1.2.0") in your app.gradle file

Then, in your instrumented test, import ActivityScenario and launch the activity with:

import androidx.test.core.app.ActivityScenario

@Test
fun testActivity() {
    ActivityScenario.launch(MainActivity::class.java).onActivity { activity ->
        // do something with your activity instance
    }
}
like image 134
Nicola Gallazzi Avatar answered Oct 04 '22 15:10

Nicola Gallazzi


Solved! Here's what I did:

  • In the test project, I removed the target package in the instrumentation tab and added it again pointing to the test project base package, where the mock activity is.
  • Added the target library as an Android Library in my test project. (Right click over test project -> properties -> Android -> Library -> added the target library).
  • Added the mock activity in the test project manifest.
  • To solve the exception I posted above, just replaced the test case constructor with this one:

        public LibraryTest() {
            super(MockActivity.class);
        }
    

Now that works and I can successfully launch dialogs. But in my brief research I stumbled upon Robotium. This library is just amazing. It is not needed for what I was trying to do at first, but I found it very useful to test GUIs in an automated way. Now I have a fresh new activity recreated in each setUp call.

like image 40
Mister Smith Avatar answered Oct 04 '22 13:10

Mister Smith