Przewodnik po testowaniu śladu

Jedną z zalet korzystania ze platform wstrzykiwania zależności takich jak Hilt jest to, że testowanie kodu jest łatwiejsze.

Testy jednostkowe

Hilt nie jest potrzebny w przypadku testów jednostkowych, ponieważ w przypadku testowania klasy korzystającej z wstrzykiwania konstruktora nie trzeba używać Hilt do utworzenia instancji tej klasy. Zamiast tego możesz bezpośrednio wywoływać konstruktor klas, przekazując fałszywe lub pozorowane zależności, tak jak gdyby nie było do niego adnotacji:

Kotlin

@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

class AnalyticsAdapterTest {

  @Test
  fun `Happy path`() {
    // You don't need Hilt to create an instance of AnalyticsAdapter.
    // You can pass a fake or mock AnalyticsService.
    val adapter = AnalyticsAdapter(fakeAnalyticsService)
    assertEquals(...)
  }
}

Java

@ActivityScope
public class AnalyticsAdapter {

  private final AnalyticsService analyticsService;

  @Inject
  AnalyticsAdapter(AnalyticsService analyticsService) {
    this.analyticsService = analyticsService;
  }
}

public final class AnalyticsAdapterTest {

  @Test
  public void happyPath() {
    // You don't need Hilt to create an instance of AnalyticsAdapter.
    // You can pass a fake or mock AnalyticsService.
    AnalyticsAdapter adapter = new AnalyticsAdapter(fakeAnalyticsService);
    assertEquals(...);
  }
}

Kompleksowe testy

W przypadku testów integracji Hilt wprowadza zależności tak samo jak w kodzie produkcyjnym. Testowanie za pomocą Hilt nie wymaga konserwacji, ponieważ Hilt automatycznie generuje nowy zestaw komponentów do każdego testu.

Dodawanie zależności testowych

Aby użyć Hilt w testach, umieść w projekcie zależność hilt-android-testing:

Odlotowy

dependencies {
    // For Robolectric tests.
    testImplementation 'com.google.dagger:hilt-android-testing:2.44'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.44'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.44'


    // For instrumented tests.
    androidTestImplementation 'com.google.dagger:hilt-android-testing:2.44'
    // ...with Kotlin.
    kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.44'
    // ...with Java.
    androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.44'
}

Kotlin

dependencies {
    // For Robolectric tests.
    testImplementation("com.google.dagger:hilt-android-testing:2.44")
    // ...with Kotlin.
    kaptTest("com.google.dagger:hilt-android-compiler:2.44")
    // ...with Java.
    testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.44")


    // For instrumented tests.
    androidTestImplementation("com.google.dagger:hilt-android-testing:2.44")
    // ...with Kotlin.
    kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.44")
    // ...with Java.
    androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.44")
}

Konfiguracja testu interfejsu użytkownika

Do każdego testu interfejsu, który używa Hilt, musisz dodać adnotacje z atrybutem @HiltAndroidTest. Odpowiada ona za generowanie komponentów Hilt dla każdego testu.

Musisz też dodać do klasy testowej HiltAndroidRule. Służy on do zarządzania stanem komponentów i służy do wprowadzania w teście wstrzykiwania:

Kotlin

@HiltAndroidTest
class SettingsActivityTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // UI tests here.
}

Java

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule
  public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  // UI tests here.
}

Następnie test musi znać klasę Application, która jest wygenerowana automatycznie przez Hilt.

Aplikacja testowa

Musisz wykonać testy instrumentalne, które wykorzystują Hilt, w obiekcie Application obsługującym Hilt. Biblioteka udostępnia HiltTestApplication do użycia w testach. Jeśli testy wymagają innej aplikacji podstawowej, zapoznaj się z artykułem Niestandardowa aplikacja do testów.

Musisz ustawić aplikację testową tak, aby uruchamiała się w testach instrumentalnych lub testach Roobolectric. Podane niżej instrukcje nie dotyczą Hilt, ale zawierają ogólne wskazówki dotyczące określania niestandardowej aplikacji uruchamianej w testach.

Ustawianie aplikacji testowej w testach instrumentalnych

Aby używać aplikacji testowej Hilt w testach instrumentalnych, musisz skonfigurować nowy proces uruchamiający testy. Dzięki temu Hilt będzie pracować we wszystkich testach instrumentalnych w Twoim projekcie. Wykonaj te czynności:

  1. Utwórz klasę niestandardową, która rozszerza zakres AndroidJUnitRunner w folderze androidTest.
  2. Zastąp funkcję newApplication i przekaż nazwę wygenerowanej aplikacji testowej Hilt.

Kotlin

// A custom runner to set up the instrumented application class for tests.
class CustomTestRunner : AndroidJUnitRunner() {

    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
        return super.newApplication(cl, HiltTestApplication::class.java.name, context)
    }
}

Java

// A custom runner to set up the instrumented application class for tests.
public final class CustomTestRunner extends AndroidJUnitRunner {

  @Override
  public Application newApplication(ClassLoader cl, String className, Context context)
      throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    return super.newApplication(cl, HiltTestApplication.class.getName(), context);
  }
}

Następnie skonfiguruj ten proces uruchamiający w pliku Gradle zgodnie z opisem w instrukcyjnym przewodniku po testowaniu jednostkowym. Użyj pełnej ścieżki klasy:

Odlotowy

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner "com.example.android.dagger.CustomTestRunner"
    }
}

Kotlin

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner"
    }
}
Ustawianie aplikacji testowej w testach Robolectric

Jeśli do testowania warstwy UI używasz Robolectric, możesz w pliku robolectric.properties określić aplikację, której chcesz użyć:

application = dagger.hilt.android.testing.HiltTestApplication

Możesz też skonfigurować aplikację oddzielnie w każdym teście, używając adnotacji @Config firmy Robolectric:

Kotlin

@HiltAndroidTest
@Config(application = HiltTestApplication::class)
class SettingsActivityTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // Robolectric tests here.
}

Java

@HiltAndroidTest
@Config(application = HiltTestApplication.class)
class SettingsActivityTest {

  @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  // Robolectric tests here.
}

Jeśli używasz wtyczki Androida do obsługi Gradle w wersji starszej niż 4.2, włącz przekształcanie klas @AndroidEntryPoint w testach jednostek lokalnych, stosując tę konfigurację w pliku build.gradle modułu:

Odlotowy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Więcej informacji na temat metody enableTransformForLocalTests znajdziesz w dokumentacji Hilt.

Funkcje testowania

Gdy firma Hilt będzie gotowa do użycia w testach, możesz dostosować proces testowania za pomocą kilku funkcji.

Typy wstrzykiwania w testach

Aby wstawić typy do testu, użyj polecenia @Inject do wstrzykiwania pól. Aby polecić Hilt, że ma wypełnić pola @Inject, wywołaj hiltRule.inject().

Oto przykład testu z instrumentacją:

Kotlin

@HiltAndroidTest
class SettingsActivityTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  @Inject
  lateinit var analyticsAdapter: AnalyticsAdapter

  @Before
  fun init() {
    hiltRule.inject()
  }

  @Test
  fun `happy path`() {
    // Can already use analyticsAdapter here.
  }
}

Java

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  @Inject AnalyticsAdapter analyticsAdapter;

  @Before
  public void init() {
    hiltRule.inject();
  }

  @Test
  public void happyPath() {
    // Can already use analyticsAdapter here.
  }
}

Zastępowanie wiązania

Jeśli chcesz wstrzykiwać fałszywą lub pozorowaną instancję zależności, poinformuj Hilt, że ma nie używać wiązania użytego w kodzie produkcyjnym, a zamiast tego użyć innego. Aby zastąpić wiązanie, które zawiera, modułem testowym zawierającym wiązania, których chcesz użyć w teście.

Załóżmy na przykład, że Twój kod produkcyjny deklaruje powiązanie AnalyticsService w ten sposób:

Kotlin

@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

Java

@Module
@InstallIn(SingletonComponent.class)
public abstract class AnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

Aby zastąpić w testach wiązanie AnalyticsService, utwórz nowy moduł Hilt w folderze test lub androidTest z fałszywą zależność i dodaj do niego adnotację @TestInstallIn. Zamiast tego do wszystkich testów w tym folderze stosowana jest fałszywa zależność.

Kotlin

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [AnalyticsModule::class]
)
abstract class FakeAnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    fakeAnalyticsService: FakeAnalyticsService
  ): AnalyticsService
}

Java

@Module
@TestInstallIn(
    components = SingletonComponent.class,
    replaces = AnalyticsModule.class
)
public abstract class FakeAnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    FakeAnalyticsService fakeAnalyticsService
  );
}

Zastępowanie wiązania w jednym teście

Aby zastąpić wiązanie w jednym teście, a nie we wszystkich testach, odinstaluj moduł Hilt z testu za pomocą adnotacji @UninstallModules i utwórz w teście nowy moduł testowy.

Korzystając z przykładu AnalyticsService z poprzedniej wersji, zacznij od polecenia klientowi Hilt, aby ignorował moduł produkcyjny, używając adnotacji @UninstallModules w klasie testowej:

Kotlin

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsActivityTest { ... }

Java

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
public final class SettingsActivityTest { ... }

Następnie trzeba zastąpić wiązanie. Utwórz w klasie testowej nowy moduł, który definiuje wiązanie testu:

Kotlin

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsActivityTest {

  @Module
  @InstallIn(SingletonComponent::class)
  abstract class TestModule {

    @Singleton
    @Binds
    abstract fun bindAnalyticsService(
      fakeAnalyticsService: FakeAnalyticsService
    ): AnalyticsService
  }

  ...
}

Java

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
public final class SettingsActivityTest {

  @Module
  @InstallIn(SingletonComponent.class)
  public abstract class TestModule {

    @Singleton
    @Binds
    public abstract AnalyticsService bindAnalyticsService(
      FakeAnalyticsService fakeAnalyticsService
    );
  }
  ...
}

Zastępuje to tylko powiązanie dla jednej klasy testowej. Jeśli chcesz zastąpić wiązanie dla wszystkich klas testowych, użyj adnotacji @TestInstallIn z sekcji powyżej. Wiązanie testowe możesz też umieścić w module test (w przypadku testów robolectric) lub w module androidTest (testy instrumentalne). Zalecamy, aby w miarę możliwości używać elementu @TestInstallIn.

Wiązanie nowych wartości

Użyj adnotacji @BindValue, aby łatwo powiązać pola w teście z wykresem zależności Hilt. Jeśli dodasz do pola adnotacje o polu @BindValue, zostanie ono powiązane z zadeklarowanym typem pola wszelkimi kwalifikatorami występującymi w jego przypadku.

W przykładzie AnalyticsService możesz zastąpić AnalyticsService fałszywą wartością, używając elementu @BindValue:

Kotlin

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsActivityTest {

  @BindValue @JvmField
  val analyticsService: AnalyticsService = FakeAnalyticsService()

  ...
}

Java

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
class SettingsActivityTest {

  @BindValue AnalyticsService analyticsService = FakeAnalyticsService();

  ...
}

Upraszcza to zarówno zastąpienie powiązania, jak i odwoływanie się do niego w teście, ponieważ umożliwia wykonywanie obu tych czynności w tym samym czasie.

Funkcja @BindValue współpracuje z kwalifikatorami i innymi adnotacjami dotyczącymi testowania. Jeśli używasz np. bibliotek testowych takich jak Mockito, możesz użyć ich w teście Robolectric w taki sposób:

Kotlin

...
class SettingsActivityTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Java

...
class SettingsActivityTest {
  ...
  @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable;

  // Robolectric tests here
}

Jeśli chcesz dodać powiązanie zbiorcze, możesz użyć adnotacji @BindValueIntoSet i @BindValueIntoMap zamiast @BindValue. @BindValueIntoMap wymaga dodania do pola adnotacji klucza mapy.

Przypadki szczególne

Hilt udostępnia też funkcje obsługujące niestandardowe przypadki użycia.

Niestandardowa aplikacja do testów

Jeśli nie możesz użyć HiltTestApplication, ponieważ Twoja aplikacja testowa musi rozszerzyć inną aplikację, dodaj adnotacje do nowej klasy lub interfejsu za pomocą @CustomTestApplication, przekazując wartość klasy podstawowej, którą wygenerowana aplikacja Hilt ma rozszerzać.

@CustomTestApplication wygeneruje klasę Application gotową do testowania za pomocą Hilt, która rozszerzy aplikację podaną przez Ciebie jako parametr.

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

W tym przykładzie Hilt generuje Application o nazwie HiltTestApplication_Application, który rozszerza klasę BaseApplication. Ogólnie nazwa wygenerowanej aplikacji to nazwa klasy z adnotacjami uzupełnionej o _Application. Musisz ustawić wygenerowaną aplikację do testów Hilt, która będzie uruchamiana w testach instruktażowych lub testach Robolectric zgodnie z opisem w sekcji Aplikacja testowa.

Wiele obiektów TestRule w teście z instrumentacją

Jeśli w teście masz inne obiekty TestRule, możesz na kilka sposobów zapewnić, że wszystkie reguły będą ze sobą działać.

Możesz połączyć te reguły w ten sposób:

Kotlin

@HiltAndroidTest
class SettingsActivityTest {

  @get:Rule
  var rule = RuleChain.outerRule(HiltAndroidRule(this)).
        around(SettingsActivityTestRule(...))

  // UI tests here.
}

Java

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule public RuleChain rule = RuleChain.outerRule(new HiltAndroidRule(this))
        .around(new SettingsActivityTestRule(...));

  // UI tests here.
}

Możesz też używać obu reguł na tym samym poziomie, o ile reguła HiltAndroidRule jest wykonywana jako pierwsza. Określ zamówienie wykonania za pomocą atrybutu order w adnotacji @Rule. Działa to tylko w JUnit w wersji 4.13 lub nowszej:

Kotlin

@HiltAndroidTest
class SettingsActivityTest {

  @get:Rule(order = 0)
  var hiltRule = HiltAndroidRule(this)

  @get:Rule(order = 1)
  var settingsActivityTestRule = SettingsActivityTestRule(...)

  // UI tests here.
}

Java

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule(order = 0)
  public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  @Rule(order = 1)
  public SettingsActivityTestRule settingsActivityTestRule = new SettingsActivityTestRule(...);

  // UI tests here.
}

launchFragmentInContainer

Nie można użyć elementu launchFragmentInContainer z biblioteki androidx.fragment:fragment-testing z Hilt, ponieważ bazuje on na działaniu, które nie ma adnotacji @AndroidEntryPoint.

Użyj kodu launchFragmentInHiltContainer z repozytorium architecture-samples na GitHubie.

Użyj punktu wejścia, zanim komponent singleton będzie dostępny

Adnotacja @EarlyEntryPoint zapewnia wyjście awaryjne, gdy trzeba utworzyć punkt wejścia Hilt, zanim komponent singleton stanie się dostępny w teście skoku.

Więcej informacji na temat właściwości @EarlyEntryPoint znajdziesz w dokumentacji Hilt.