Przewodnik po testowaniu śladu

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

Testy jednostkowe

Hilt nie jest potrzebny w przypadku testów jednostkowych, ponieważ podczas testowania klasy, która korzysta z wstrzykiwania przez konstruktor, 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 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 samo 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 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.56.2'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.56.2'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.56.2'


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

Kotlin

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


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

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 generuje automatycznie.

Testowanie aplikacji

Testy z instrumentacją, które korzystają z Hilt, musisz wykonywać w obiekcie Application obsługującym Hilt. Biblioteka udostępnia HiltTestApplication do użycia 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 ramach testów z użyciem instrumentacji lub testów 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 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 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 musisz 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 nim 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 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();

  ...
}

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ę do nowej klasy lub interfejsu za pomocą @CustomTestApplication, 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 z użyciem instrumentacji 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 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 repozytorium launchFragmentInHiltContainer architecture-samples GitHub.

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

Adnotacja @EarlyEntryPoint zapewnia możliwość wyjścia z sytuacji, 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 Hilt.