I run a specific unit-test:
#Rule
public ActivityScenarioRule<FragmentUtilActivity> activityScenarioRule2 =
new ActivityScenarioRule<>(FragmentUtilActivity.class);
#Before
public void setUp() {
... //not related to activityScenarioRule2
}
#Test
#Config(qualifiers = "sw600dp")
public void myTest() {
activityScenarioRule2
.getScenario()
.onActivity(
activity ->
standaloneAccountMenuDialogFragment.showNow(
activity.getSupportFragmentManager(), "FragmentTag"));
assertThat(...);
}
I see the #after code is called (with one breakpoint which turned into two breakpoints)
and I get this runtime error:
FragmentManager is already executing transactions
java.lang.IllegalStateException:
at android.support.v4.app.FragmentManagerImpl.ensureExecReady(FragmentManagerImpl.java:1551)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1611)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManagerImpl.java:137)
at android.os.Handler.handleCallback(Handler.java:790)
How can it be if I use activityScenario which is defined once and managed by #Rule annotation? It fails only for this specific test
Any ideas what can it be?
I have seen this issue with Robolectric and ActivityScenario, and it appears to be a race condition where the fragment isn't attached by the time we get to it. There's a warning about this in the default (LEGACY) LooperMode, which is what is used if a mode isn't specified. More details on that in the javadoc here
Try adding #LooperMode(LooperMode.Mode.PAUSED) to the top of your test class, which has some improvements on the Looper behavior. You may also need to add a shadowOf(getMainLooper()).idle() call in the teardown method.
Another workaround, if that doesn't help, is to add sdk=[27] to the config at the top of the test class like this-
#Config(sdk = [27])
There are more details on this issue - https://github.com/robolectric/robolectric/issues/3698
Related
I am using Espresso with Kotlin for the UI test automation. I am trying to find a proper way to restart the app during the test and start it again, so the test scenario is the following:
start the app, go to login page
force close the app and open it again (basically restart it)
check some stuff etc
The way our UI tests are organized:
there is a test class where I have rules
val intent = Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java)
.putExtra(UI_TEST_INTENT, true)
#get:Rule
val rule = ActivityScenarioRule<MainActivity>(intent)
there Before/After functions and tests functions in this class
What I want is to have generic restartApp function in separated class, let's say TestUtils and to be able to call it at any point of time, when is needed.
So far I didn't find a solution. There are some similar questions on stackoverflow, but I am not sure I understand how to work with the answers I found, like this:
with(activityRule) {
finishActivity()
launchActivity(null)
}
Since ActivityTestRule is deprecated and documentation asking to use ActivityScenarioRule, I tried this:
#get:Rule
val rule = ActivityScenarioRule<MainActivity>(intent)
private fun restart() {
rule.scenario.close()
rule.scenario.recreate()
}
but it gets java.lang.NullPointerException
another option is
private fun restart() {
pressBackUnconditionally()
Intents.release()
ActivityScenario.launch<MainActivity>(intent)
}
it works, app restarts but I can not interact with the app anymore, because for some reason there are two intents running now
Would be great to get an answer I can work with (I am quite new to Espresso)
Cheers
The solution is found:
private fun restart() {
Intents.release()
rule.scenario.close()
Intents.init()
ActivityScenario.launch<MainActivity>(intent)
}
Seems like the author's answer has some excess code. The following is enough
activityScenarioRule.scenario.close()
ActivityScenario.launch(YourActivity::class.java, null)
Hello I have the following project structure
--App
|--SDK1
|--SDK2
In app I have some test for check SDK1 and SDK2.
In SDK I have a singleton pattern only to set the context by the application class that is in App.
And the context is set in the SDK1.singleton in the App.Application.onCreate
The problem is that when I try to execute the following code I always get null:
#RunWith(RobolectricTestRunner.class)
public class CallTest {
#Before
public void setUp() throws Exception {}
#Test
public void connectToSocketTest() {
if (BuildConfig.FLAVOR.equals("dev")) {
Context context = SDK1.getInstance().getContext();
assertNotNull(context);
...
Any idea why this happens, and how can solve it?
That's not the recommended way to get app context in Android using robolectric. You can get your activity by using below code...
Activity activity = Robolectric.setupActivity(MyActivity.class);
To get the app context, just call activity.getApplicationContext().
EDIT1: If you're using the latest Robolectric version, use
Robolectric.buildActivity(DashboardActivity.class) instead.
EDIT2: Make sure your SDK1 extends MultiDexApplication". Add this #Config(manifest=Config.NONE, application = App.class, sdk = 17)` to
the top of your test class.
Let me know if it works.
I have a testsuite which has mutiple testcases in a class
every test case is isolated
So when i execute the testsuite class i want to restart the app for every testcase
How do i relaunch application from start for every individual test case in Espresso
Thanks in advance
#Test
public void testcase1() {
//from first screen
}
#Test
public void testcase2() {
//from first screen
}
There is another stack overflow answer that seems to answer this question. If you were looking to do that in Kotlin though I converted the answer to relaunch multiple times for different tests.
#RunWith(AndroidJUnit4::class)
class ExampleEspressoTest {
#get:Rule
val rule = ActivityTestRule(
activityClass = MainActivity::class.java,
initialTouchMode = false,
launchActivity = false) //set to false to customize intent
#Test
fun testCustomIntent() {
val intent = Intent().apply {
putExtra("your_key", "your_value")
}
rule.launchActivity(intent)
//continue with your test
}
}
If you need to start a method/test and when it's finished clear data and start the next one, you should use commands.
Look at this documentation: https://developer.android.com/studio/test/command-line
I'm using this command:
./gradlew testVariantNameUnitTest --tests *.sampleTestMethod
There could be several ways to do this but we wanted a way that works both locally as well as google fire base test lab, so ended up with using configuration in build.gradle file under default config.
defaultConfig{
testInstrumentationRunnerArguments clearPackageData: 'true'
}
Reference: https://developer.android.com/training/testing/junit-runner#ato-gradle
Also you use these runner arguments for configuring different tests you wanted run based on build variants or other config options, look at my post if you want more detail.
I'm trying to carry out junit test for the Android-DDP library.
To initialize the meteor object, we need a reference to a android context which I'm able to achieve using Robolectric. But the web-sockets is probably talking to the server on a different thread because of which the callback methods are not called and the test methods are getting end.
I used netstat to check if the android client is trying to communicate or not. It shows various ping/pong messages. So, Yes it is trying to talk to the server.
I went through this tutorial as well,
Android AsyncTask testing with Android Test Framework. This one tells how to handle the network on UI thread. But nothing seems right.
The sample code, I have worked is:
#Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP)
#RunWith(RobolectricGradleTestRunner.class)
public class MainActivityTest {
private MainActivity activity;
private Meteor meteor;
private String globalUrl = "ws://10.0.3.222:3000/websocket";
#Before
public void setup() {
activity = Robolectric.setupActivity(MainActivity.class);
meteor = new Meteor(activity, globalUrl);
meteor.reconnect();
/*
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
}
#Test
public void validateMeteorIsConnected() {
assertTrue(meteor.isConnected());
}
}
Any help would be appreciable. Thanks in advance.
You defined two methods, setup() and validateMeteorIsConnected(), but where are they called?
First, your setup is not correct. After your call to new Meteor(...), you don't need the reconnect() call because the constructor does already establish the connection.
Moreover, you must set up a listener so that you know when the connection has been established or data comes in. This is done with mMeteor.setCallback(...); where the parameter is this or activity.
As you said, the work is done on a different thread and everything is asynchronous.
So you can't just call validateMeteorIsConnected() immediately after connecting.
You need some timer, as shown in the question that you linked to.
I run my Android tests by running a test implementation which "derives" from a library project (because I have a multi module project with baselib and "concrete app projects"). The test implementation is one of these concrete app projects and is launched by an InstrumentationTestCase. In this test case I mock several parts from the library project by RoboGuice. That means I run a "real" implementation of my baselib with mocked classes (like persistence handling, database handling and so on). To be able to do that, every single test case has to close and restart the whole test instance, because I can't start the same app twice on the device. These test are more integration tests than Junit tests, because I test some kind of workflows, but there is no other possibility to test that, because the possibilities with JUnit on Android testing seem to be very limited.
At the moment I can only run one test case at the same time, because if I run more than 1, the whole test is hanging. I already checked if it's the configuration change (see private method) which causes my test to freeze, but this is not the cause. See my attempts in tearDown method. I can't run
getInstrumentation().finish(0, new Bundle());
because I get
Test failed to run to completion. Reason: 'Test run failed to
complete. Expected 3 tests, received 1'
I also cannot run
getInstrumentation().callActivityOnDestroy(activity);
because I don't have an Activity here. Moreover the Activity "StartTestActivity" which is launched at startup is not the same Activity which runs when the test is finished because StartTestActivity launches another Activity "MainMenuActivity" which is running at the end of the test. I already thought about using Instrumentation.ActivityMonitor but this doesn't provide the needed functionality.
Nevertheless I want to somehow start with the same test conditions at every test case as the whole test itself does at startup, but I'm not sure what InstrumentationTestCase is doing in the background, so I don't know how to restart the whole instrumentation setup. I somehow need to stop and restart the test instance, or maybe there is a better solution? Any ideas?
(by the way: every test itself runs fine, so it's no problem of the test ifself).
public class WorkflowModule1Test extends InstrumentationTestCase
{
private PersistenceManagerMock persistenceManager;
#Override
protected void setUp() throws Exception
{
super.setUp();
}
#Override
protected void tearDown() throws Exception
{
super.tearDown();
if (persistenceManager != null)
{
persistenceManager.clear();
}
}
public void testSaveLocaleEN() throws PersistenceException
{
updateLocaleConfiguration(Locale.ENGLISH);
Intent intent = new Intent(getInstrumentation().getContext(), StartTestActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getInstrumentation().startActivitySync(intent);
persistenceManager = (PersistenceManagerMock)RoboGuice.getInjector(ContextProvider.getApplication()).getInstance(IPersistenceManager.class);
List<Entity> entities = persistenceManager.getEntities();
assertTrue(entities.size() == 1);
assertTrue(entities.get(0) instanceof LanguageUsageRel);
assertTrue(((LanguageUsageRel)entities.get(0)).getLanguageId().equals("EN"));
}
public void testSaveLocaleDE() throws PersistenceException
{
updateLocaleConfiguration(Locale.GERMAN);
Intent intent = new Intent(getInstrumentation().getContext(), StartTestActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getInstrumentation().startActivitySync(intent);
persistenceManager = (PersistenceManagerMock)RoboGuice.getInjector(ContextProvider.getApplication()).getInstance(IPersistenceManager.class);
List<Entity> entities = persistenceManager.getEntities();
assertTrue(entities.size() == 1);
assertTrue(entities.get(0) instanceof LanguageUsageRel);
assertTrue(((LanguageUsageRel)entities.get(0)).getLanguageId().equals("DE"));
}
private void updateLocaleConfiguration(Locale locale)
{
Locale.setDefault(locale);
Configuration configuration = new Configuration();
configuration.locale = locale;
getInstrumentation().getContext().getResources().updateConfiguration(configuration, getInstrumentation().getContext().getResources().getDisplayMetrics());
}
}
I think if you extended ActivityInstrumentationTestCase2 instead this would solve a lot of your problems.
Another note: Put your tear down logic before the super.tearDown() call.
I found the solution on my own. I have to set these two flags.
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);