J'essaie d'écrire un test d'instrumentation pour mon Android.
Je rencontre des problèmes de filetage étranges et je n'arrive pas à trouver de solution.
Mon test d'origine:
@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {
@Rule
public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);
@Test
public void loadWorkOrder_displaysCorrectly() throws Exception {
final WorkOrderDetails activity = activityRule.getActivity();
WorkOrder workOrder = new WorkOrder();
activity.updateDetails(workOrder);
//Verify customer info is displayed
onView(withId(R.id.customer_name))
.check(matches(withText("John Smith")));
}
}
Il en est résulté une
Android.view.ViewRootImpl $ CalledFromWrongThreadException: seul le thread d'origine qui a créé une hiérarchie de vues peut toucher ses vues.
...
com.kwtree.kwtree.workorder.WorkOrderDetails.updateDetails (WorkOrderDetails.Java:155)
La seule chose que fait la méthode updateDetails()
est quelques appels setText()
.
Après quelques recherches, il semblait que l'ajout d'une annotation UiThreadTestRule
et Android.support.test.annotation.UiThreadTest
À mon test résoudrait le problème.
@ UiThreadTest:
@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {
//Note: This is new
@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
@Rule
public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);
@Test
@UiThreadTest //Note: This is new
public void loadWorkOrder_displaysCorrectly() throws Exception {
final WorkOrderDetails activity = activityRule.getActivity();
WorkOrder workOrder = new WorkOrder();
activity.updateDetails(workOrder);
//Verify customer info is displayed
onView(withId(R.id.customer_name))
.check(matches(withText("John Smith")));
}
}
Java.lang.IllegalStateException: la méthode ne peut pas être appelée sur le thread d'application principal (on: main)
(Remarque: toutes les méthodes de cette trace de pile ne sont pas mon code)
Cela semble me donner des résultats mitigés ... S'il doit être exécuté sur le thread d'origine qui a créé les vues mais ne peut pas s'exécuter sur le thread principal, sur quel thread doit-il être exécuté?
J'apprécierais vraiment toute aide ou suggestion!
Ces tests d'instrumentation s'exécutent dans leur propre application. Cela signifie également qu'ils s'exécutent dans leur propre thread .
Vous devez considérer votre instrumentation comme quelque chose que vous installez à côté de votre application réelle, de sorte que vos interactions possibles sont "limitées".
Vous devez appeler toutes les méthodes de vue depuis le thread UIThread/main de l'application, donc appeler activity.updateDetails(workOrder);
depuis votre thread d'instrumentation n'est pas pas le thread principal de l'application. C'est pourquoi l'exception est levée.
Vous pouvez simplement exécuter le code dont vous avez besoin pour tester sur votre thread principal comme vous le feriez si vous l'appeliez dans votre application à partir d'un thread différent en utilisant
activity.runOnUiThread(new Runnable() {
public void run() {
activity.updateDetails(workOrder);
}
}
Avec cela en cours, votre test devrait fonctionner.
L'exception d'état illégal que vous recevez semble être due à votre interaction avec la règle. Les états documentation
Notez que les méthodes d'instrumentation ne peuvent pas être utilisées lorsque cette annotation est présente.
Si vous démarrez/commencez votre activité en @Before
ça devrait aussi marcher.
Vous pouvez exécuter une partie de votre test sur le thread d'interface utilisateur principal à l'aide de UiThreadTestRule.runOnUiThread(Runnable)
:
@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
@Test
public void loadWorkOrder_displaysCorrectly() throws Exception {
final WorkOrderDetails activity = activityRule.getActivity();
uiThreadTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
WorkOrder workOrder = new WorkOrder();
activity.updateDetails(workOrder);
}
});
//Verify customer info is displayed
onView(withId(R.id.customer_name))
.check(matches(withText("John Smith")));
}
Dans la plupart des cas, il est plus simple d'annoter la méthode de test avec UiThreadTest
, cependant, cela peut entraîner d'autres erreurs telles que Java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)
.
Pour l'ex-République yougoslave de Macédoine, voici une citation du Javadoc de UiThreadTest
:
Notez qu'en raison de la limitation actuelle de JUnit, les méthodes annotées avec
Before
etAfter
seront également exécutées sur le thread d'interface utilisateur. Envisagez d'utiliser runOnUiThread (Runnable) si c'est un problème.
Veuillez noter que UiThreadTest
(package Android.support.test.annotation
) Mentionné ci-dessus est différent de ( UiThreadTest
(package Android.test
)).
La réponse acceptée est maintenant obsolète
La façon la plus simple d'y parvenir est d'utiliser simplement UiThreadTest
import Android.support.test.annotation.UiThreadTest;
@Test
@UiThreadTest
public void myTest() {
// Set up conditions for test
// Call the tested method
activity.doSomethingWithAView()
// Verify that the results are correct
}
La réponse acceptée décrit parfaitement ce qui se passe.
En outre, au cas où quelqu'un serait curieux de savoir pourquoi les méthodes d'Espresso qui touchent l'interface utilisateur, par exemple perform(ViewActions ...)
n'a pas besoin de faire de même, c'est simplement parce qu'ils finissent par le faire plus tard pour nous.
Si vous suivez perform(ViewActions ...)
vous constaterez qu'il finit par faire ce qui suit (dans Android.support.test.espresso.ViewInteraction
):
private void runSynchronouslyOnUiThread(Runnable action) {
...
mainThreadExecutor.execute(uiTask);
...
}
Ce mainThreadExecutor
est lui-même annoté avec @MainThread
.
En d'autres termes, Espresso doit également jouer selon les mêmes règles décrites par David sur la réponse acceptée.