Przewodnik po testowaniu śladu

Jedną z zalet korzystania z platform wstrzykiwania zależności, takich jak Hilt, jest możliwość łatwiejszego testowania kodu.

Testy jednostkowe

Wskaźnik nie jest wymagany w przypadku testów jednostkowych, ponieważ podczas testowania klasy korzystającej z wstrzykiwania konstruktora nie trzeba używać Hilt do utworzenia jej wystąpienia. Zamiast tego możesz bezpośrednio wywołać konstruktor klas, przekazując fałszywe lub pozorowane zależności, tak jak w przypadku braku 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 w taki sam sposób, jak w kodzie produkcyjnym. Testowanie za pomocą narzędzia Hilt nie wymaga obsługi, ponieważ do każdego testu automatycznie generuje nowy zestaw komponentów.

Dodaję zależności testowe

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

Odlotowe

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

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

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

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órą Hilt generuje automatycznie.

Testuj aplikację

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

Musisz skonfigurować aplikację testową tak, aby była uruchamiana w testach instrumentalnych lub testach Robolectric. Poniższe instrukcje nie dotyczą tylko Hilt, ale są to ogólne wskazówki dotyczące wybierania aplikacji niestandardowych do uruchamiania w testach.

Ustawienie aplikacji testowej w testach instrumentowanych

Aby używać aplikacji testowej Hilt w testach instrumentalnych, musisz skonfigurować nowy program uruchamiający test. Dzięki temu Hilt będzie działać we wszystkich testach zinstrumentowanych w 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 mechanizm uruchamiania testów w pliku Gradle zgodnie z opisem w instrumentowanym przewodniku do testów jednostkowych. Pamiętaj, aby użyć 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 w pliku robolectric.properties określić, której aplikacji chcesz użyć:

application = dagger.hilt.android.testing.HiltTestApplication

Możesz też skonfigurować aplikację w każdym teście oddzielnie, używając 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łcanie klas @AndroidEntryPoint w testach jednostek lokalnych, stosując tę konfigurację w pliku build.gradle modułu:

Odlotowe

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Więcej informacji o enableTransformForLocalTests znajdziesz w dokumentacji Hilt.

Funkcje testowe

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

Wstrzykiwanie typów w testach

Aby wstrzykiwać typy do testu, używaj @Inject do wstrzykiwania pól. Aby poprosić Hilt o wypełnienie pól @Inject, wywołaj hiltRule.inject().

Zobacz przykład testu zinstruowanego:

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 poinstruować Hilt, aby nie używał powiązania użytego w kodzie produkcyjnym i zamiast tego używało innego. Aby zastąpić powiązanie, musisz zastąpić moduł, który je zawiera, modułem testowym zawierającym powiązania, których chcesz używać w teście.

Na przykład załóżmy, że Twój kod produkcyjny deklaruje powiązanie dla AnalyticsService w następujący 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 w folderze test lub androidTest z fałszywą zależność i dodaj do niej adnotację @TestInstallIn. Wszystkie testy w tym folderze są wstrzykiwane z fałszywą 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 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 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 musisz zastąpić powią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 powiązanie tylko dla jednej klasy testowej. Jeśli chcesz zastąpić powiązanie dla wszystkich klas testowych, użyj adnotacji @TestInstallIn z sekcji powyżej. Powiązanie testowe możesz też umieścić w module test (w przypadku testów Robolectric) lub w module androidTest (w przypadku testów zinstruowanych). Zalecamy, aby w miarę możliwości używać pola @TestInstallIn.

Powiązanie nowych wartości

Użyj adnotacji @BindValue, aby łatwo powiązać pola testu z grafem zależności Hilt. Dodaj do pola adnotację @BindValue, aby będzie ono objęte zadeklarowanym typem pola ze wszystkimi kwalifikatorami, które występują w tym polu.

W przykładzie AnalyticsService możesz zastąpić element AnalyticsService fałszywym, używając parametru @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, ponieważ umożliwia wykonanie obu tych czynności jednocześnie.

@BindValue działa z kwalifikatorami i innymi adnotacjami testowymi. Jeśli np. używasz bibliotek testowych, takich jak Mockito, w teście Robolectric możesz wykorzystać te biblioteki 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 chcesz dodać wielowiązywanie, zamiast @BindValue możesz użyć adnotacji @BindValueIntoSet i @BindValueIntoMap. @BindValueIntoMap wymaga dodania adnotacji do pola z adnotacją klucza mapy.

Przypadki szczególne

Hilt udostępnia też funkcje, które ułatwiają obsługę niestandardowych przypadków użycia.

Niestandardowa aplikacja do testów

Jeśli nie możesz użyć HiltTestApplication, ponieważ aplikacja testowa musi rozszerzyć inną aplikację, dodać adnotację do nowej klasy lub interfejsu za pomocą @CustomTestApplication, przekazując wartość klasy bazowej, którą ma rozszerzyć 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 obiekt Application o nazwie HiltTestApplication_Application, który rozszerza klasę BaseApplication. Ogólnie nazwa wygenerowanej aplikacji to nazwa klasy z adnotacjami z dodanym _Application. Musisz skonfigurować wygenerowaną aplikację testową Hilt tak, aby była uruchamiana w testach instrumentalnych lub testach Robolectric zgodnie z opisem w sekcji Aplikacja testowa.

Wiele obiektów TestRule w teście instrumentalnym

Jeśli testujesz inne obiekty TestRule, masz kilka możliwości zapewnienia, że wszystkie reguły będą ze sobą współpracować.

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żyć obu reguł na tym samym poziomie, o ile reguła HiltAndroidRule zostanie wykonana jako pierwsza. Określ kolejność wykonania, używając 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żywać właściwości launchFragmentInContainer z biblioteki androidx.fragment:fragment-testing za pomocą Hilta, ponieważ opiera się na działaniu, które nie jest oznaczone adnotacją @AndroidEntryPoint.

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

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

Adnotacja @EarlyEntryPoint zapewnia dostęp awaryjny, gdy trzeba utworzyć punkt wejścia Hilt, zanim komponent singleton będzie dostępny w teście Hilt.

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