2016-07-11 7 views
1

Итак, я работаю над этим небольшим проектом, который использует Dagger 2 для инъекций зависимостей и Realm как базу данных.Модульное тестирование Realm + Dagger 2 с Robolectric & Mockito

Я тестирую устройство с помощью Robolectric и Mockito (с Powermock). Из предыдущих исследований (и много боли) я понял, что тестирование Realm довольно трудоемко, но было сделано в прошлом here.

Теперь мой проект имеет очень похожую настройку и структуру, связанную выше.

Когда я бегу мои модульных тестов, все они проходят за исключением одного, что дает мне очень зашифрованное сообщение, которое выглядит следующим образом:

java.lang.NullPointerException 
at org.robolectric.internal.ShadowExtractor.extract(ShadowExtractor.java:5) 
at org.robolectric.Shadows.shadowOf(Shadows.java:1190) 
at org.robolectric.shadows.CoreShadowsAdapter.getMainLooper(CoreShadowsAdapter.java:37) 
at org.robolectric.util.ComponentController.<init>(ComponentController.java:31) 
at org.robolectric.util.ComponentController.<init>(ComponentController.java:23) 
at org.robolectric.util.ActivityController.<init>(ActivityController.java:40) 
at org.robolectric.util.ActivityController.of(ActivityController.java:32) 
at org.robolectric.Robolectric.buildActivity(Robolectric.java:82) 
at org.robolectric.Robolectric.buildActivity(Robolectric.java:78) 
at org.robolectric.Robolectric.setupActivity(Robolectric.java:86) 
at uk.co.placona.tradesafe.view.EditActivityTest.ActivityShouldNotBeNull(EditActivityTest.java:54) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
at java.lang.reflect.Method.invoke(Method.java:498) 
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68) 
at org.powermock.modules.junit4.internal.impl 

Строка кода, указанного на ошибки выше:

activity = Robolectric.setupActivity(EditActivity.class); 

Активность существует, и в нее вводится TradeRepository, когда она запускается.

Указанный вид деятельности можно найти here вместе с остальной частью кода. Я пытался отладить это в течение 3 дней без успеха. Каждый отдельный тестовый тест, который я создаю, отлично работает, за исключением любого модульного теста, который используется Activity, что заставляет меня думать, что я, вероятно, отсутствует что-то действительно очевидное.

Буду рад прояснить любые вопросы здесь. С большим спасибо!

+0

Вы запускаете его с помощью PowerMock? –

+0

Да, как вы можете видеть здесь: https://github.com/mplacona/trade-safe/blob/master/app/src/test/java/uk/co/placona/tradesafe/view/EditActivityTest.java –

+0

Да , уже проверено.Это всегда сложно с «PowerMock» –

ответ

2

статично злобно, власть - это зло :).

Я думаю, вам следует избавиться от вашего инжектора. Это вам не нужно, потому что у вас есть только один объект CustomApplication во время вашего приложения.

Вы должны изменить код следующим образом:

В CustomApplication.Java, компонент приложения создается, устанавливается в переменном поле, и вводили

private ApplicationComponent applicationComponent; 

public void setup(){ 
     getOrCreateApplicationComponent().inject(this); 
     databaseRealm.setup(); 
     stethoDebug.setup(this); 
    } 

public ApplicationComponent getOrCreateApplicationComponent() { 
     if (applicationComponent == null) { 
      applicationComponent = DaggerApplicationComponent.builder() 
        .applicationContextModule(new ApplicationContextModule(this)) 
        .repositoryModule(new RepositoryModule()) 
        .build(); 
     } 

     return applicationComponent; 
    } 

В способах OnCreate из CreateActivity, EditActivity и MainActivity, форсунка заменяется

((CustomApplication) getApplication()) 
      .getOrCreateApplicationComponent() 
      .inject(this); 

В RepositoryModule мы будем использовать Кинжал 2, чтобы придать зависимости в конструкторы поэтому нам не нужно вводить вручную контекст и DatabaseRealm

@Provides 
@Singleton 
public TradeRepository provideTradeRepository(DatabaseRealm databaseRealm) { 
    return new TradeRepositoryImpl(databaseRealm); 
} 

@Provides 
@Singleton 
public DatabaseRealm provideDatabaseRealm(Context context) { 
    return new DatabaseRealm(context); 
} 

затем в Da tabaseRealm мы добавим конструктор с контекстом в качестве параметра

Context mContext; 

RealmConfiguration realmConfiguration; 

public DatabaseRealm(Context context) { 
    mContext = context; 
} 

и же в TradeRepositoryImpl, конструктор с databaseRealm добавляется

DatabaseRealm databaseRealm; 

public TradeRepositoryImpl(DatabaseRealm databaseRealm) { 
    this.databaseRealm = databaseRealm; 
} 

Для RepositoryTestModule, мы добавим databaseRealm в качестве параметра:

@Provides 
@Singleton 
public TradeRepository provideTradeRepository(DatabaseRealm databaseRealm) { 
    return isMocked ? mock(TradeRepository.class) : new TradeRepositoryImpl(databaseRealm); 
} 

в вашем TestCustomApplication мы переопределяем getOrCreateApplicationComponent

@Override 
    public ApplicationComponent getOrCreateApplicationComponent() { 
     return DaggerApplicationComponentTest.builder() 
       .applicationContextModuleTest(new ApplicationContextModuleTest()) 
       .repositoryModuleTest(new RepositoryModuleTest(false)) 
       .build(); 
    } 

Теперь для каждого из ваших тестов мы бежим их с RobolectricGradleTestRunner и добавить TestCustomApplication.class в качестве приложения тег

@RunWith(RobolectricGradleTestRunner.class) 
@Config(constants = BuildConfig.class, sdk = 21, application = TestCustomApplication.class) 

, когда необходимо придать зависимостей в наших тестах мы будем вводить так:

@Before 
public void setupDagger() { 
    DaggerApplicationComponentTest.builder() 
      .applicationContextModuleTest(new ApplicationContextModuleTest()) 
      .repositoryModuleTest(new RepositoryModuleTest(false)) 
      .build().inject(this); 
} 

у нас еще есть NullPointerException в нашем EditActivityTest, потому что эта линия:

loadTrade(intent.getExtras().getString("ID")); 

Либо вы проверяете, что цель не является нулевой, либо вы предоставили ее в своем тесте.

+0

Обычно я создаю 'Test ', который автоматически загружается Robolectric, чтобы избежать «CustomTestApplication» и дублирования логики для установки инъекций , Но может быть супер хорошей идеей. Я переосмыслию свой подход –

+0

Wooow! Вы просто отвели меня прямо в школу сюда! Спасибо вам большое за это. Читая код, он имеет гораздо больше смысла и выглядит намного чище! И да .. статично .. расскажи мне об этом! Хотелось бы, чтобы у меня был способ ответить +1 еще раз! Такой большой и полный ответ! Большое вам спасибо за то, что нашли время, я очень благодарен! –

2

Глядя на свой тестовый класс, я не вижу, чтобы вы использовали какой-либо из Robolectric тест-бегунов.

Вы должны использовать RobolectricGradleTestRunner или RobolectricTestRunner, чтобы вызвать функциональные возможности Robolecric о загрузке манифеста, разбор ресурсов, создание основного петлителя и т.д.

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

Так же, Robolectric и PowerMock изменяют Java ClassLoader обоих. Вот почему так сложно (возможно, невозможно) заставить их работать вместе. Поэтому проверьте @Steve ответ, как изменить свой код, чтобы удалить PowerMock необходимость для вашего теста.

+0

Спасибо, Евгений, очень признателен за ваш ответ здесь. Я изменил этот код во многих отношениях, я в какой-то момент изменил бегун и даже не осознал этого. Знайте, когда вы начинаете менять все по прихоти, чтобы узнать, не имеет ли что-либо значение? Это был я вчера :-). Поистине оцените ваши комментарии и предложения! –

+0

Добро пожаловать! Спасибо за комментарий и высокую оценку @ steve-c –