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 mock zależności, tak jak w przypadku, gdy konstruktor nie jest opatrzony adnotacjami:

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 jest używany 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 użyciem instrumentacji. Upewnij się, że używasz pełnej ścieżki 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 interfejsu użytkownika 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 testu z użyciem instrumentacji:

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

Za pomocą adnotacji @BindValue możesz łatwo powiązać pola w teście z grafem zależności Hilta. 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 dołączonym znakiem _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 owinąć 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ż opiera się ona na aktywności, która nie jest opatrzona adnotacjami @AndroidEntryPoint.

Zamiast tego użyj kodu launchFragmentInHiltContainerz 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.