Przewodnik po testowaniu śladu

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

Testy jednostkowe

Hilt nie jest wymagany w przypadku testów jednostkowych, ponieważ przy testowaniu klasy, która używa argumentu konstruktora, nie musisz używać Hilt do tworzenia instancji. Zamiast tego możesz bezpośrednio wywołać konstruktor klas, przekazując wartość „fałsz” lub „imitację” 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(...);
  }
}

Kompleksowe testy

W przypadku testów integracji Hilt wstrzykuje zależności tak samo jak w środowisku produkcyjnym w kodzie. Testowanie za pomocą Hilt nie wymaga konserwacji, ponieważ Hilt automatycznie powoduje wygenerowanie nowego zestawu komponentów dla każdego testu.

Dodaję zależności testowe

Aby używać Hilt do testowania, uwzględnij zależność hilt-android-testing w parametrze projekt:

Odlotowe

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

Do każdego testu interfejsu, który używa Hilt z funkcją @HiltAndroidTest, musisz dodać adnotacje. Ta adnotacja odpowiada za generowanie komponentów Hilta dla każdego testu.

Musisz też dodać HiltAndroidRule do klasy testowej. Zarządza komponentów i służy do wykonywania 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 obsługującą Hilt generowane automatycznie.

Testuj aplikację

Musisz wykonać testy zinstrumentowane korzystające z Hilt w obiekcie Application który obsługuje Hilt. Biblioteka udostępnia funkcje HiltTestApplication do wykorzystania w testach. Jeżeli testy wymagają innej aplikacji podstawowej, zapoznaj się z sekcją Aplikacja niestandardowa do .

Należy ustawić aplikację testową tak, aby uruchamiała się w Testy lub Robolectric Nie można wykonywać tych czynności: dotyczące Hilt, ale zawierają ogólne wskazówki dotyczące do testowania.

Ustawienie aplikacji testowej w testach instrumentowanych

Korzystanie z aplikacji testowej Hilt w instrumentach testów, musisz skonfigurować nowy moduł uruchamiający test. Dzięki temu Hilt będzie działać we wszystkich testach zinstrumentowanych w projekcie. Wykonaj te kroki:

  1. Tworzenie niestandardowej klasy z rozszerzeniem AndroidJUnitRunner in folder androidTest.
  2. Zastąp funkcję newApplication i przekaż nazwę wygenerowanego Aplikacja testowa 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 mechanizm uruchamiania testów w pliku Gradle zgodnie z opisem w test jednostkowy instrumentów . Upewnij się, użyj pełnej ścieżki klasy:

Odlotowe

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"
    }
}
Ustaw aplikację testową w testach Robolectric

Jeśli używasz Robolectric do testowania warstwy interfejsu, możesz określić, która aplikacja do użycia w pliku robolectric.properties:

application = dagger.hilt.android.testing.HiltTestApplication

Możesz też skonfigurować aplikację w każdym teście pojedynczo, za pomocą adnotacji @Config 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łcenie klas @AndroidEntryPoint w testy jednostkowe lokalnej przez zastosowanie tę konfigurację w pliku build.gradle modułu:

Odlotowe

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Więcej informacji o enableTransformForLocalTests znajdziesz w Hilt dokumentacji.

Funkcje testowe

Gdy Hilt będzie gotowy do użycia w testach, możesz zacząć korzystać z kilku funkcji, dostosować proces testowania.

Wstrzykiwanie typów w testach

Aby wstrzykiwać typy do testu, używaj @Inject do wstrzykiwania pól. Aby nakazywać Hiltowi wypełnij pola @Inject, wywołaj hiltRule.inject().

Zobacz ten przykład testu instrumentowanego:

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 wstrzyknąć fałszywą lub pozorowaną instancję zależności, musisz przekazać Nie należy używać powiązania użytego w kodzie produkcyjnym oraz inny plik. Aby zastąpić powiązanie, musisz zastąpić moduł, który zawiera powiązanie z modułem testowym, który zawiera wybrane powiązania. których można 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ć powiązanie AnalyticsService w testach, utwórz nowy moduł Hilt folder test lub androidTest z fałszywą zależnością i dodaj do niej adnotacje dzięki @TestInstallIn. Wszystkie testy w tym folderze wstrzykiwane są przy użyciu fałszywych 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, a nie we wszystkich testach, odinstaluj Hilt z testu przy użyciu adnotacji @UninstallModules i utwórz nowy moduł z modułu testowego.

Korzystając z przykładu AnalyticsService z poprzedniej wersji, zacznij od polecenia Wskazuje, że moduł produkcyjny jest ignorowany za pomocą adnotacji @UninstallModules na zajęciach testowych:

Kotlin

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

Java

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

Następnie musisz zastąpić powiązanie. Utwórz nowy moduł w ramach klasy testowej definiujące 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
    );
  }
  ...
}

Zastępuje to powiązanie tylko dla jednej klasy testowej. Jeśli chcesz zastąpić dla wszystkich klas testowych, użyj adnotacji @TestInstallIn z tagu powyżej. Możesz też umieścić testowe powiązanie w module test w przypadku testów Robolectric, a w module androidTest – do testów instrumentowanych. Zalecamy, aby w miarę możliwości używać pola @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ę do pola @BindValue, aby je objąć zadeklarowany typ pola ze wszystkimi kwalifikatorami, które są w nim obecne.

W przykładzie AnalyticsService możesz zamienić element AnalyticsService na fałszywka, 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();

  ...
}

Upraszcza to zarówno zastąpienie wiązania, jak i odwoływanie się do niego w teście umożliwiając jednoczesne korzystanie z obu tych funkcji.

@BindValue działa z kwalifikatorami i innymi adnotacjami testowymi. Przykład: jeśli używasz bibliotek testowych takich jak Mockito, możesz go użyć w Ten test Roboelektryczny:

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ć wielowiązywanie, możesz stosować adnotacje @BindValueIntoSet i @BindValueIntoMap z @BindValue. @BindValueIntoMap wymaga dodania adnotacji do pola z adnotacją 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ć usługi HiltTestApplication, ponieważ aplikacja testowa musi: rozszerzania innej aplikacji, dodawania adnotacji do nowej klasy lub interfejsu @CustomTestApplication, przekazując wartość klasy bazowej, którą chcesz ustawić Wygenerowano aplikację Hilt do rozszerzenia.

@CustomTestApplication wygeneruje klasę Application gotową do testowania za pomocą Hilt, które rozszerza aplikację przekazaną jako parametr.

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

W przykładzie Hilt generuje pole Application o nazwie HiltTestApplication_Application rozszerzający klasę BaseApplication. W ogólny, nazwa wygenerowanej aplikacji jest nazwą z adnotacjami zajęcia z dodanym elementem _Application. Musisz ustawić wygenerowany test Hilt aplikację do uruchomienia w testach instrumentalnych lub Testy Robolectric, zgodnie z opisem w sekcji Testy aplikacji.

Wiele obiektów TestRule w teście instrumentalnym

Jeśli masz w teście inne obiekty TestRule, możesz to zrobić na kilka sposobów. aby zapewnić, że wszystkie reguły będą ze sobą współ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 HiltAndroidRule jest uruchamiany jako pierwszy. Określ zamówienie wykonania za pomocą Atrybut order w adnotacji @Rule. Działa to tylko w wersji JUnit 4.13 lub nowszy:

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ć usługi launchFragmentInContainer z biblioteka androidx.fragment:fragment-testing z Hilt, ponieważ opiera się ona na działanie, które nie ma adnotacji @AndroidEntryPoint.

Użyj launchFragmentInHiltContainer z interfejsu architecture-samples GitHub .

Aby udostępnić komponent singleton, użyj punktu wejścia

Adnotacja @EarlyEntryPoint zapewnia dostęp awaryjny, gdy wpis Hilt aby komponent singleton był dostępny w Test wierzchołkowy.

Więcej informacji o @EarlyEntryPoint w Dokumentacja Hilt