Java.util.ConcurrentModificationException в тестах JUnit

Это один из выстрелов в темноте. Иногда я получаю следующую ошибку при запуске моего robolectric unit test:

java.util.ConcurrentModificationException
  at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:966)
  at java.util.LinkedList$ListItr.next(LinkedList.java:888)
  at org.robolectric.shadows.ShadowResources.getOverlayedThemeValue(ShadowResources.java:294)
  at org.robolectric.shadows.ShadowResources.findAttributeValue(ShadowResources.java:284)
  at org.robolectric.shadows.ShadowResources.attrsToTypedArray(ShadowResources.java:187)
  at org.robolectric.shadows.ShadowResources.access$000(ShadowResources.java:51)
  at org.robolectric.shadows.ShadowResources$ShadowTheme.obtainStyledAttributes(ShadowResources.java:489)
  at android.content.res.Resources$Theme.obtainStyledAttributes(Resources.java)
  at android.content.Context.obtainStyledAttributes(Context.java:416)
  at android.view.View.__constructor__(View.java:3317)
  at org.robolectric.util.ReflectionHelpers$3.run(ReflectionHelpers.java:144)
  at org.robolectric.util.ReflectionHelpers.traverseClassHierarchy(ReflectionHelpers.java:241)
  at org.robolectric.util.ReflectionHelpers.callInstanceMethod(ReflectionHelpers.java:138)
  at org.robolectric.internal.Shadow.invokeConstructor(Shadow.java:73)
  at org.robolectric.shadows.ShadowView.__constructor__(ShadowView.java:109)
  at android.view.View.<init>(View.java)
  at android.widget.TextView.<init>(TextView.java)
  at com.getbase.floatingactionbutton.FloatingActionsMenu.createLabels(FloatingActionsMenu.java:461)
  at com.getbase.floatingactionbutton.FloatingActionsMenu.onFinishInflate(FloatingActionsMenu.java:447)
  at android.view.LayoutInflater.rInflate(LayoutInflater.java:763)
  at android.view.LayoutInflater.rInflate(LayoutInflater.java:758)
  at android.view.LayoutInflater.rInflate(LayoutInflater.java:758)
  at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
  at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
  at android.view.LayoutInflater.inflate(LayoutInflater.java:353)
  at android.support.v7.app.ActionBarActivityDelegateBase.setContentView(ActionBarActivityDelegateBase.java:228)
  at android.support.v7.app.ActionBarActivity.setContentView(ActionBarActivity.java:102)
  at com.myapp.app.view.activity.MainActivityActivity.onCreate(MainActivityActivity.java:75)
  at android.app.Activity.performCreate(Activity.java:5133)
  at org.robolectric.util.ReflectionHelpers$3.run(ReflectionHelpers.java:144)
  at org.robolectric.util.ReflectionHelpers.traverseClassHierarchy(ReflectionHelpers.java:241)
  at org.robolectric.util.ReflectionHelpers.callInstanceMethod(ReflectionHelpers.java:138)
  at org.robolectric.util.ActivityController$1.run(ActivityController.java:114)
  at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:309)
  at org.robolectric.shadows.CoreShadowsAdapter$2.runPaused(CoreShadowsAdapter.java:47)
  at org.robolectric.util.ActivityController.create(ActivityController.java:110)
  at com.myapp.app.BaseActivityRobolectricTest.startActivity(BaseActivityRobolectricTest.java:58)
  at com.myapp.app.BaseActivityRobolectricTest.startActivity(BaseActivityRobolectricTest.java:34)
  at com.myapp.app.view.activity.MainActivityActivityTest.setupActivity(MainActivityActivityTest.java:52)
  at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
  at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
  at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
  at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:234)
  at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
  at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:167)
  at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
  at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:86)
  at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
  at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:69)
  at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:48)
  at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
  at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
  at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
  at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
  at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
  at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:105)
  at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
  at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
  at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:360)
  at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
  at java.lang.Thread.run(Thread.java:745)

У меня есть ужасное время, освещающее эту стеклянную трассу и что может происходить. Кто-нибудь сталкивался с этим и нашел решение?

Я нашел один вопрос о Robolectric github, который предлагает сделать clean перед выполнением тестов. Хотя кажется, что он минимизирует количество раз, когда этот тест выходит из строя, он по-прежнему не работает.

MainActivityActivity.java:75 setContentView(R.layout.activity_main);

Тест:

@Before
public void setupActivity() {
    setupAuthObjectGraphWithFakeData();
    startActivity(RequestReviewsActivity.class, new ParentModule(), null);
}

protected void startActivity(Class<T> activityClass, Object module, Intent intent) {
    mActivityController = buildActivity(activityClass);

    if(intent != null) {
        mActivityController.withIntent(intent);
    }

    mActivity = mActivityController.get();

    List<Object> modules = new ArrayList<>();
    modules.addAll(mActivity.getModules());
    modules.add(module);

    ObjectGraph objectGraph;
    if(mActivity instanceof AuthedActivity) {
        objectGraph = BaseApplication.getInstance().getAuthObjectGraph().plus(modules.toArray());
    } else {
        objectGraph = BaseApplication.getInstance().getAppObjectGraph().plus(modules.toArray());
    }
    mActivity.setObjectGraph(objectGraph);

    mActivityController.create(mBundle).start().resume();
}

@Test
public void testViewsAreAvailable() {
    assertThat(getActivity().mToolbar).isNotNull();
    assertThat(getActivity().mContent).isNotNull();
    assertThat(getActivity().mContentShadow).isNotNull();
    assertThat(getActivity().mRequestRootFab).isNotNull();
    assertThat(getActivity().mRequestReviewsList).isNotNull();
    assertThat(getActivity().mRequestReviewsMainContent).isNotNull();
}

Update:

Эта проблема исчезает, если не выполняется специализированные модульные тесты (похожие на следующие). Пока я должен был прокомментировать эти тесты.

Я обычно следую найденному здесь методу Robolectric - http://blog.blundell-apps.com/android-gradle-app-with-robolectric-junit-tests/ с некоторыми обновлениями (поскольку эта статья немного устарела).

Я выполняю тесты с помощью ./gradlew test

Ответ 1

Если вы используете что-то вроде Fabric или Crashlytics, обязательно отключите его в тестах Robolectric. Мы столкнулись с очень похожими проблемами и с помощью некоторой локальной отладки, мы смогли найти поток, который вызывал проблему. Когда Fabric инициализируется, он запускает фоновый поток, который обращается к некоторым ресурсам. Моя мысль состоит в том, что это прежде всего ошибка с Robolectric в том смысле, что она не обрабатывает загрузку ресурсов в потокобезопасной манере, но в среде тестирования не требуется Crashlytics, поэтому это быстрое решение.

Как правило, пользователь будет инициализировать Fabric или Crashlytics в своем классе Application. Robolectric позволяет вам создать класс "Test" Application, префикс вашего текущего класса словом "Test". В исходном классе Application создайте отдельный метод инициализации системы отчетов о сбоях. В TestApplication переопределите этот метод и убедитесь, что он пуст, поэтому в ваших тестах не инициализируется Ткань. Например:

App.java

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        setupCrashReporting();
    }

    protected void setupCrashReporting() {
        CrashlyticsCore core = new CrashlyticsCore.Builder()
                .disabled(BuildConfig.DEBUG)
                .build();

        Crashlytics crashlytics = new Crashlytics.Builder()
                .core(core)
                .build();

        Fabric.with(this, crashlytics);
    }
}

TestApp.java

public class TestApp extends App {
    @Override
    protected void setupCrashReporting() {
        // Do nothing.
    }
}

Это помогло решить проблему в наших приложениях.