Как получить новый экземпляр класса Application для каждого unit test?

У меня есть приложение для Android, у которого есть класс MyApplication, который наследуется от Application.

Я создал несколько модульных тестов, которые работают с @RunWith(AndroidJUnit4.class). Если я запускаю каждый тест отдельно, все они проходят. Если я запускаю их вместе - сначала один проходит, а затем (некоторые из) других терпят неудачу.

Проблема заключается в том, что создается только один экземпляр MyApplication, а затем он сохраняется и используется для всех тестов, что вызывает сбой, поскольку в MyApplication есть состояние, которое ДОЛЖНО быть инициализировано только один раз.

Есть ли способ запуска модульных тестов (androidTest), чтобы приложение перезапускалось для каждого теста? Мне все равно, будет ли он медленным (например, приложение нужно будет переустанавливать каждый раз) Я просто хочу, чтобы тесты запускались независимо друг от друга.

Фактический код из unit test выглядит (по запросу @Zinc):

@RunWith(AndroidJUnit4.class)
public class AutoLogin_ActMainTest {
    @Rule
    public ActivityTestRule<ActMain> mActivityRule = new ActivityTestRule<ActMain>(
            ActMain.class) {


        @Override
        protected void beforeActivityLaunched() {
            super.beforeActivityLaunched();

            MyTestApp app = (MyTestApp) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
            DependencyInjector.reset();
            app.reset();


            FakeUnitDaggerModule fudm = new FakeUnitDaggerModule();

            Session session = new SessionImpl(new TimeProviderImpl());
            fudm.setResMain(new ResMainTest(session));

            FakeAppPrefs appPrefs = new FakeAppPrefs();
            FakeLoginPrefs loginPrefs = new FakeLoginPrefs();
            CurrentUserHolder currentUserHolder = new CurrentUserHolder();

            FakeComponent inj = DaggerFakeComponent.builder().
                    fakeMyAppDaggerModule(new FakeMyAppDaggerModule(app, appPrefs, loginPrefs, currentUserHolder)).
                    appInfoDaggerModule(new AppInfoDaggerModule("1")).
                    fakeSessionDaggerModule(new FakeSessionDaggerModule(session)).
                    fakeExchangeDaggerModule(new FakeExchangeDaggerModule("https://test.com")).
                    fakeUnitDaggerModule(fudm).
                    build();

            DependencyInjector.init(inj);
            DependencyInjector.getInstance().inject(app);


            app.onStart();
        }
    };


    @Test
    public void testAutoLogin() {
        ElapsedTimeIdlingResource idlingResource = new ElapsedTimeIdlingResource(500);
        Espresso.registerIdlingResources(idlingResource);
        idlingResource.startWaiting();

        onView(ViewMatchers.withId(R.id.tv_logged_in_as)).check(matches(isDisplayed()));
        Espresso.unregisterIdlingResources(idlingResource);
    }
}

Ответ 1

Проблема заключается в том, что создается только один экземпляр MyApplication, а затем он сохраняется и используется для всех тестов, что вызывает сбой, потому что в MyApplication есть состояние, которое ДОЛЖНО быть инициализировано только один раз.

ИМХО, что ошибка в приложении, которая должна быть исправлена. Application является неприемлемым местом для значительной истинной бизнес-логики (хотя это нормально для инициализации вашей библиотеки crash-reporter, StrictMode и т.д.). Все остальное должно быть проверено индивидуально, либо напрямую, либо через mocks, либо через инъекцию зависимости, и т.д.

При этом иногда проблема заключается не в коде, который вы контролируете, а скорее в коде из библиотек или в рамках фреймворка.

Есть ли способ запуска модульных тестов (androidTest), чтобы приложение перезапускалось для каждого теста?

В настоящее время да, хотя в то время этого вопроса не задавали. Android Test Orchestrator (ATO) делает лучшую работу по изоляции каждого теста за счет скорости выполнения теста.

Ответ 2

Вам нужно реорганизовать свое приложение, чтобы какой-либо контролируемый статус не привязан к классу приложения, а другому объекту. Затем вы можете reset удалить эту часть или издеваться над ней, не заботясь о сохранении класса приложения. Предпочтительно это делается с использованием инъекции зависимости определенной формы.

Ответ 3

Я не совсем уверен в твоем вопросе. Но вы можете использовать ApplicationTestCase. Что-то вроде:

public class MyApplicationTest extends ApplicationTestCase<MyTestApp> {
    public void test1() {
        createApplication();

        ... test here ...

        terminateApplication();
    }

    public void test2() {
        createApplication();

        ... test here ...

        terminateApplication();
    }
}

Ссылка: https://developer.android.com/reference/android/test/ApplicationTestCase.html

Ответ 4

    public class TestApplication extends Application {

@Override
public void onCreate() {
    super.onCreate();
    // Sdk.terminate(); - If you specify TestApplication as an 
    //                    application class in AndroidManifest, 
    //                    you'll have to uncomment this(due to issue with test runner)
    Sdk.initialize();
}

@Override
public void onTerminate() {
    super.onTerminate();
    Sdk.terminate();
}
}

Класс Sdk

    public class Sdk {

private static Sdk sInstance;
private void Sdk(){
}

public static Sdk getInstance() throws RuntimeException {
    if (sInstance == null) {
        throw new RuntimeException();
    }
    return sInstance;
}

public static void terminate() {
    sInstance = null;
}

public static void initialize() {
    if (sInstance == null) {
        sInstance = new Sdk();
        //save some information according to what is on the default configurations
    } else {
        throw new RuntimeException("Method was already initialized");
    }
}}

Тесты:

    public class MyApplicationTest extends ApplicationTestCase<TestApplication> {

public MyApplicationTest() {
    super(TestApplication.class);
}

public void testMultiplicationTests() {
    createApplication();

    int answer = 42;
    assertEquals(42, answer);

    terminateApplication();
}


public void testDefaultSettings() {
    createApplication();

    assertNotNull(Sdk.getInstance());

    terminateApplication();
}}