Przewodnik po testowaniu śladu

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

Testy jednostkowe

Hilt nie jest potrzebny w przypadku testów jednostkowych, ponieważ podczas testowania klasy, która korzysta z wstrzykiwania w konstruktorze, nie musisz używać Hilta do utworzenia jej instancji. Zamiast tego możesz bezpośrednio wywołać konstruktor klasy, przekazując fałszywe lub pozorowane zależności, tak jak w przypadku, gdyby konstruktor nie był opatrzony adnotacją:

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 samo jak w kodzie produkcyjnym. Testowanie za pomocą Hilta nie wymaga konserwacji, ponieważ Hilt automatycznie generuje nowy zestaw komponentów dla każdego testu.

Dodawanie zależności testowych

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.57.1'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.57.1'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.57.1'


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

Kotlin

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


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

Konfiguracja testu interfejsu

Każdy test interfejsu, który korzysta z Hilt, musi być oznaczony adnotacją @HiltAndroidTest. Ta adnotacja odpowiada 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 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 automatycznie generuje.

Testowanie aplikacji

Testy z instrumentacją, które korzystają z Hilt, musisz wykonywać w obiekcie Application obsługującym Hilt. Biblioteka udostępnia HiltTestApplication do wykorzystania w testach. Jeśli testy wymagają innej aplikacji bazowej, zapoznaj się z artykułem Aplikacja niestandardowa na potrzeby testów.

Aplikację testową musisz skonfigurować tak, aby działała w testach z instrumentacją lub testach Robolectric. Poniższe instrukcje nie są specyficzne dla Hilta, ale zawierają ogólne wskazówki dotyczące określania niestandardowej aplikacji do uruchamiania w testach.

Ustawianie aplikacji testowej w testach z instrumentacją

Aby używać aplikacji testowej Hilt w testach z instrumentacją, musisz skonfigurować nowy program uruchamiający testy. 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ą, która rozszerza 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 program do uruchamiania testów w pliku Gradle zgodnie z opisem w przewodniku po testach jednostkowych z instrumentacją. 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"
    }
}
Ustawianie aplikacji testowej w testach Robolectric

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

application = dagger.hilt.android.testing.HiltTestApplication

Możesz też skonfigurować aplikację w każdym teście 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 Androida do obsługi Gradle w wersji starszej niż 4.2, włącz przekształcanie klas @AndroidEntryPoint w lokalnych testach jednostkowych, stosując w pliku build.gradle modułu tę konfigurację:

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Więcej informacji o enableTransformForLocalTests znajdziesz w dokumentacji Hilt.

Funkcje testowania

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

Wstrzykiwanie typów w testach

Aby wstrzyknąć typy do testu, użyj @Inject do wstrzykiwania pól. Aby poinformować Hilta, ż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 wstrzyknąć fałszywą lub testową instancję zależności, musisz poinformować Hilt, aby nie używał powiązania, którego używał w kodzie produkcyjnym, i zamiast tego użył innego. Aby zastąpić powiązanie, musisz zastąpić moduł, który zawiera 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 powiązanie dla 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 w folderze test lub androidTest z fałszywą zależnością i dodaj do niego adnotację @TestInstallIn. Zamiast tego do wszystkich testów w tym folderze wstrzykiwana 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 powiązania w jednym teście

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

Zgodnie z AnalyticsServiceprzykładem z poprzedniej wersji zacznij od poinformowania Hilta, aby zignorował moduł produkcyjny, używając w klasie testowej adnotacji @UninstallModules:

Kotlin

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

Java

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

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

Zastępuje to tylko powiązanie dla jednej klasy testowej. Jeśli chcesz zastąpić wiązanie we wszystkich klasach testowych, użyj adnotacji @TestInstallIn z sekcji powyżej. Możesz też umieścić testowe powiązanie w module test w przypadku testów Robolectric lub w module androidTest w przypadku testów z użyciem instrumentacji. Zalecamy używanie @TestInstallIn, kiedy tylko jest to możliwe.

Powiązywanie nowych wartości

Użyj adnotacji @BindValue, aby łatwo powiązać pola w teście z grafem zależności Hilt. Oznacz pole za pomocą symbolu @BindValue, a zostanie ono powiązane z zadeklarowanym typem pola z wszelkimi kwalifikatorami, które są w nim obecne.

W przykładzie AnalyticsService możesz zastąpić AnalyticsService fałszywą wartością, 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ępowanie powiązania, jak i odwoływanie się do niego w teście, ponieważ obie te czynności można wykonać jednocześnie.

@BindValue współpracuje z kwalifikatorami i innymi adnotacjami testowymi. Jeśli na przykład używasz bibliotek testowych, takich jak Mockito, możesz ich użyć 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 chcesz dodać wielokrotne powiązanie, możesz użyć adnotacji @BindValueIntoSet@BindValueIntoMap zamiast @BindValue. @BindValueIntoMap wymaga też dodania do pola adnotacji z kluczem mapy.

Przypadki szczególne

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

Aplikacja niestandardowa do testów

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

@CustomTestApplication wygeneruje klasę Application gotową do testowania za pomocą Hilta, 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 klasę Application o nazwie HiltTestApplication_Application, która rozszerza klasę BaseApplication. Ogólnie rzecz biorąc, nazwa wygenerowanej aplikacji to nazwa klasy z adnotacjami z dodanym znakiem _Application. Wygenerowaną aplikację testową Hilt musisz skonfigurować tak, aby działała w testach instrumentalnych lub testach Robolectric, zgodnie z opisem w sekcji Aplikacja testowa.

Wiele obiektów TestRule w teście instrumentowanym

Jeśli w teście masz inne obiekty TestRule, istnieje kilka sposobów, aby upewnić się, że wszystkie reguły działają razem.

Możesz połączyć 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 najpierw zostanie wykonana reguła HiltAndroidRule. Określ kolejność wykonywania 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żywać launchFragmentInContainer z biblioteki androidx.fragment:fragment-testing z Hiltem, ponieważ zależy ona od aktywności, która nie jest oznaczona adnotacją @AndroidEntryPoint.

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

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

Adnotacja @EarlyEntryPoint zapewnia możliwość wyjścia z sytuacji, gdy punkt wejścia Hilt musi zostać utworzony, zanim komponent singleton będzie dostępny w teście Hilt.

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