Przewodnik po testowaniu śladu

Jedną z zalet korzystania z ramek wstrzykiwania zależności, takich jak Hilt, jest ułatwienie testowania kodu.

Testy jednostkowe

Hilt nie jest potrzebny do testów jednostkowych, ponieważ podczas testowania klasy, która korzysta z wstrzyknięcia konstruktora, nie musisz tworzyć instancji tej klasy za pomocą Hilta. Zamiast tego możesz bezpośrednio wywołać konstruktor klasy, przekazując fałszywe lub mockowane zależności, tak jak w przypadku konstruktora bez 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(...);
  }
}

Testy kompleksowe

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

Dodawanie zależności testowania

Aby używać Hilta w testach, dodaj do projektu zależność hilt-android-testing:

Groovy

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


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

Kotlin

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


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

Konfiguracja testu interfejsu

Każdy test UI, który korzysta z Hilt, musi być opatrzony adnotacjami @HiltAndroidTest. Ta adnotacja odpowiada za generowanie komponentów Hilta dla każdego testu.

Musisz też dodać HiltAndroidRule do klasy testowej. Zarządza stanem komponentów i służy do wstrzykiwania danych do testu:

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 wiedzieć, jak działa klasa Application, którą Hilt automatycznie dla Ciebie generuje.

Aplikacja testowa

Testy z instrumentacją, które korzystają z Hilt, muszą być wykonywane w obiekcie Application obsługującym Hilt. Biblioteka udostępnia HiltTestApplication do użytku w testach. Jeśli testy wymagają innej aplikacji podstawowej, zapoznaj się z artykułem Niestandardowa aplikacja do testów.

Aplikacja testowa musi być skonfigurowana tak, aby uruchamiać testy z użyciem instrumentacji lub testy Robolectric. Poniższe instrukcje nie dotyczą tylko Hilta, ale są ogólnymi wskazówkami dotyczącymi określania niestandardowej aplikacji do uruchamiania w ramach testów.

Konfigurowanie aplikacji testowej w testach z użyciem instrumentacji

Aby używać aplikacji testowej Hilt w testach z użyciem instrumentacji, musisz skonfigurować nowego wykonawcę testów. Dzięki temu Hilt będzie działać we wszystkich testach z instrumentacją w Twoim projekcie. Wykonaj te czynności:

  1. Utwórz klasę niestandardową rozszerzającą klasę 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 tego testującego w pliku Gradle zgodnie z opisem w przewodniku po testach jednostkowych z wykorzystaniem instrumentacji. Upewnij się, że używasz pełnej ścieżki do klasy:

Groovy

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"
    }
}
Konfigurowanie aplikacji testowej w testach Robolectric

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

application = dagger.hilt.android.testing.HiltTestApplication

Możesz też skonfigurować aplikację w ramach każdego testu z osobna, używając adnotacji @Config w 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 Android Gradle w wersji niższej niż 4.2, włącz przekształcanie klas @AndroidEntryPoint w lokalnych testach jednostkowych, stosując w pliku build.gradle w module następującą konfigurację:

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Więcej informacji o enableTransformForLocalTests znajdziesz w dokumentacji Hiltu.

Funkcje testowania

Gdy Hilt będzie gotowy do użycia w testach, możesz użyć kilku funkcji, aby dostosować proces testowania.

Wstawianie typów w testach

Aby wstrzyknąć typy do testu, użyj pola @Inject do wstrzyknięcia pól. Aby polecić Hilt wypełnienie pól @Inject, wywołaj funkcję hiltRule.inject().

Oto przykład zinstrumentowanego testu:

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 powiązania

Jeśli chcesz wstrzyknąć fałszywy lub symulowany egzemplarz zależności, musisz powiedzieć Hiltowi, aby nie używał powiązania, którego używał w kodzie produkcyjnym, a zamiast tego użył innego. Aby zastąpić powiązanie, musisz zastąpić moduł zawierający powiązanie modułem testowym zawierającym powiązania, których chcesz użyć w teście.

Załóżmy na przykład, że kod produkcyjny deklaruje ograniczenie dotyczące 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ścią i opatrz go adnotacją @TestInstallIn. Zamiast tego do wszystkich testów w tym folderze jest wstrzykiwana 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 powiązania w pojedynczym teście

Aby zastąpić powiązanie w pojedynczym teście zamiast we wszystkich testach, odinstaluj moduł Hilta z testu za pomocą adnotacji @UninstallModules i utwórz nowy moduł testu w ramach testu.

W przykładzie AnalyticsService z poprzedniej wersji zacznij od poinformowania Hilta o ignorowaniu modułu produkcyjnego za pomocą adnotacji @UninstallModules w klasie testowej:

Kotlin

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

Java

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

Następnie musisz wymienić oprawę. Utwórz nowy moduł w klasie testu, 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
    );
  }
  ...
}

Spowoduje to zastąpienie tylko powiązania dla jednej klasy testów. Jeśli chcesz zastąpić wiązanie we wszystkich klasach testu, użyj adnotacji @TestInstallIn z sekcji powyżej. Możesz też umieścić testowanie w module test w przypadku testów Robolectric lub w module androidTest w przypadku testów z użyciem instrumentacji. Zalecamy, aby w miarę możliwości używać @TestInstallIn.

Powiązanie nowych wartości

Aby łatwo łączyć pola w teście z grafem zależności Hilt, użyj adnotacji @BindValue. Dodaj adnotację @BindValue do pola, a zostanie ono powiązane z deklarowanym typem pola z wszelkimi obecnymi w nim kwalifikatorami.

W przykładzie AnalyticsService możesz zastąpić AnalyticsService wartością fałszywą, używając @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();

  ...
}

Dzięki temu możesz zastąpić powiązanie i odwoływać się do niego w testach jednocześnie.

@BindValue działa z kwalifikatorami i innymi adnotacjami testowymi. Jeśli na przykład używasz bibliotek testowych, takich jak Mockito, możesz użyć ich w teście Robolectric w ten 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 musisz dodać wiązanie wieloelementowe, możesz użyć adnotacji @BindValueIntoSet@BindValueIntoMap zamiast @BindValue. @BindValueIntoMap wymaga również adnotacji pola za pomocą adnotacji klucza mapy.

Przypadki szczególne

Hilt udostępnia też funkcje do obsługi niestandardowych przypadków użycia.

Niestandardowa aplikacja do testów

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

@CustomTestApplication wygeneruje klasę Application gotową do testowania za pomocą Hilt, która rozszerza aplikację przekazaną 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óra rozszerza klasę BaseApplication. Zazwyczaj nazwa wygenerowanej aplikacji to nazwa opatrzonej adnotacjami klasy z dodaną końcówką _Application. Wygenerowaną aplikację testową Hilt należy skonfigurować tak, aby była uruchamiana w testach z użyciem instrumentacji lub testach Robolectric zgodnie z opisem w aplikacji testowej.

Wiele obiektów TestRule w testach z użyciem instrumentacji

Jeśli w testach masz inne obiekty TestRule, możesz na kilka sposobów zadbać o to, aby wszystkie reguły działały razem.

Reguły możesz zwijać 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żyć obu reguł na tym samym poziomie, o ile reguła HiltAndroidRule zostanie wykonana jako pierwsza. Określ kolejność wykonywania za pomocą atrybutu order w adnotacji @Rule. Ta metoda działa tylko w wersji JUnit 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żywać funkcji launchFragmentInContainer z biblioteki androidx.fragment:fragment-testing w Hilt, ponieważ korzysta ona z aktywności, która nie jest opatrzona adnotacjami @AndroidEntryPoint.

Zamiast tego użyj kodu launchFragmentInHiltContainer z repozytorium GitHub architecture-samples.

Używanie punktu wejścia przed udostępnieniem komponentu singleton

Adnotacja @EarlyEntryPoint stanowi furtkę awaryjnego wyjścia, gdy punkt wejścia Hilta musi zostać utworzony, zanim komponent singleton będzie dostępny w teście Hilta.

Więcej informacji o @EarlyEntryPoint znajdziesz w dokumentacji Hiltu.