Leitfaden für Hilt-Tests

Einer der Vorteile von Abhängigkeitsinjektions-Frameworks wie Hilt besteht darin, dass sie das Testen Ihres Codes erleichtert.

Einheitentests

Für Einheitentests ist „Hilt“ nicht erforderlich, da Sie beim Testen einer Klasse, die eine Konstruktor-Injektion verwendet, nicht Hilt verwenden müssen, um diese Klasse zu instanziieren. Stattdessen können Sie einen Klassenkonstruktor direkt aufrufen, indem Sie fiktive oder simulierte Abhängigkeiten übergeben, so als ob der Konstruktor nicht annotiert werden würde:

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(...);
  }
}

End-to-End-Tests

Bei Integrationstests fügt Hilt Abhängigkeiten so wie in Ihren Produktionscode ein. Tests mit Hilt sind nicht wartungsfrei, da Hilt für jeden Test automatisch einen neuen Satz von Komponenten generiert.

Testabhängigkeiten hinzufügen

Wenn Sie Hilt in Ihren Tests verwenden möchten, fügen Sie die Abhängigkeit hilt-android-testing in Ihr Projekt ein:

Cool

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")
}

UI-Test einrichten

Sie müssen alle UI-Tests, die Hilt verwenden, mit @HiltAndroidTest annotieren. Diese Annotation ist für das Generieren der Hilt-Komponenten für jeden Test verantwortlich.

Außerdem musst du der Testklasse das HiltAndroidRule hinzufügen. Sie verwaltet den Status der Komponenten und wird verwendet, um eine Injektion in Ihren Test durchzuführen:

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.
}

Als Nächstes muss Ihr Test Informationen zur Klasse Application haben, die Hilt automatisch für Sie generiert.

Anwendung testen

Sie müssen instrumentierte Tests ausführen, die Hilt in einem Application-Objekt verwenden, das Hilt unterstützt. Die Bibliothek stellt HiltTestApplication zur Verwendung in Tests bereit. Wenn für Ihre Tests eine andere Basisanwendung erforderlich ist, finden Sie weitere Informationen unter Benutzerdefinierte Anwendung für Tests.

Sie müssen die Testanwendung so einrichten, dass sie in Ihren instrumentierten Tests oder Robolectric-Tests ausgeführt wird. Die folgende Anleitung bezieht sich nicht speziell auf Hilt, sondern ist allgemeine Richtlinien zum Angeben einer benutzerdefinierten Anwendung für Tests.

Testanwendung in instrumentierten Tests festlegen

Wenn Sie die Hilt-Testanwendung in instrumentierten Tests verwenden möchten, müssen Sie einen neuen Test-Runner konfigurieren. Daher funktioniert Hilt für alle instrumentierten Tests in Ihrem Projekt. Gehen Sie dazu so vor:

  1. Erstellen Sie eine benutzerdefinierte Klasse, die AndroidJUnitRunner im Ordner androidTest erweitert.
  2. Überschreibe die newApplication-Funktion und übergib den Namen der generierten Hilt-Testanwendung.

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);
  }
}

Konfigurieren Sie als Nächstes diesen Test-Runner in der Gradle-Datei, wie in der Anleitung zum Testen von instrumentierten Einheiten beschrieben. Achten Sie darauf, den vollständigen Klassenpfad zu verwenden:

Cool

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"
    }
}
Testanwendung in Robolectric-Tests festlegen

Wenn Sie Ihre UI-Ebene mit Robolectric testen, können Sie in der Datei robolectric.properties angeben, welche Anwendung verwendet werden soll:

application = dagger.hilt.android.testing.HiltTestApplication

Alternativ können Sie die Anwendung mit der Annotation @Config von Robolectric für jeden Test einzeln konfigurieren:

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.
}

Wenn Sie ein Android-Gradle-Plug-in vor Version 4.2 verwenden, aktivieren Sie das Transformieren von @AndroidEntryPoint-Klassen in lokalen Einheitentests. Wenden Sie dazu die folgende Konfiguration in der build.gradle-Datei Ihres Moduls an:

Cool

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Weitere Informationen zu enableTransformForLocalTests finden Sie in der Hilt-Dokumentation.

Testfunktionen

Sobald Hilt für Ihre Tests einsatzbereit ist, können Sie den Testprozess mit verschiedenen Funktionen anpassen.

Injektionstypen in Tests

Verwenden Sie @Inject für die Feldeinschleusung, um Typen in einen Test einzufügen. Rufen Sie hiltRule.inject() auf, um Hilt anzuweisen, die @Inject-Felder auszufüllen.

Hier ein Beispiel für einen instrumentierten Test:

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.
  }
}

Bindung ersetzen

Wenn Sie eine Fake- oder Simulationsinstanz einer Abhängigkeit einfügen müssen, müssen Sie Hilt anweisen, nicht die Bindung aus dem Produktionscode zu verwenden, sondern stattdessen eine andere Bindung. Um eine Bindung zu ersetzen, müssen Sie das Modul, das die Bindung enthält, durch ein Testmodul mit den Bindungen ersetzen, die Sie im Test verwenden möchten.

Angenommen, Ihr Produktionscode deklariert so eine Bindung für AnalyticsService:

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
  );
}

Wenn Sie die AnalyticsService-Bindung in Tests ersetzen möchten, erstellen Sie im Ordner test oder androidTest ein neues Hilt-Modul mit der fiktiven Abhängigkeit und annotieren Sie diese mit @TestInstallIn. Alle Tests in diesem Ordner werden stattdessen mit der fiktiven Abhängigkeit eingeschleust.

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
  );
}

Bindung in einem einzelnen Test ersetzen

Wenn Sie eine Bindung in einem einzelnen Test statt in allen Tests ersetzen möchten, deinstallieren Sie ein Hilt-Modul aus einem Test mit der Annotation @UninstallModules und erstellen Sie ein neues Testmodul innerhalb des Tests.

Folge dem AnalyticsService-Beispiel aus der vorherigen Version und weist Hilt zuerst an, das Produktionsmodul zu ignorieren. Dazu verwendet er die Annotation @UninstallModules in der Testklasse:

Kotlin

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

Java

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

Als Nächstes müssen Sie die Bindung ersetzen. Erstellen Sie in der Testklasse ein neues Modul, das die Testbindung definiert:

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
    );
  }
  ...
}

Dadurch wird nur die Bindung für eine einzelne Testklasse ersetzt. Wenn Sie die Bindung für alle Testklassen ersetzen möchten, verwenden Sie die Annotation @TestInstallIn aus dem obigen Abschnitt. Alternativ können Sie die Testbindung in das Modul test für Robolectric-Tests oder in das Modul androidTest für instrumentierte Tests einfügen. Es wird empfohlen, nach Möglichkeit @TestInstallIn zu verwenden.

Neue Werte binden

Mit der Annotation @BindValue können Sie Felder in Ihrem Test ganz einfach an das Hilt-Abhängigkeitsdiagramm binden. Wenn Sie ein Feld mit @BindValue annotieren, wird es unter dem deklarierten Feldtyp mit allen Qualifizierern gebunden, die für dieses Feld vorhanden sind.

Im Beispiel AnalyticsService können Sie AnalyticsService mithilfe von @BindValue durch eine Fälschung ersetzen:

Kotlin

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

  @BindValue @JvmField
  val analyticsService: AnalyticsService = FakeAnalyticsService()

  ...
}

Java

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

  @BindValue AnalyticsService analyticsService = FakeAnalyticsService();

  ...
}

Dies vereinfacht sowohl das Ersetzen einer Bindung als auch das Verweisen auf eine Bindung in Ihrem Test, da Sie beide gleichzeitig ausführen können.

@BindValue funktioniert mit Qualifizierern und anderen Testannotationen. Wenn Sie beispielsweise Testbibliotheken wie Mockito verwenden, können Sie sie so in einem robots.txt-Test verwenden:

Kotlin

...
class SettingsActivityTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Java

...
class SettingsActivityTest {
  ...
  @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable;

  // Robolectric tests here
}

Wenn Sie eine Multibinding hinzufügen müssen, können Sie die Annotationen @BindValueIntoSet und @BindValueIntoMap anstelle von @BindValue verwenden. Bei @BindValueIntoMap müssen Sie das Feld außerdem mit einer Zuordnungsschlüssel-Annotation annotieren.

Besondere Fälle

Hilt bietet auch Funktionen für nicht standardmäßige Anwendungsfälle.

Benutzerdefinierte Anwendung für Tests

Wenn Sie HiltTestApplication nicht verwenden können, weil Ihre Testanwendung eine andere Anwendung erweitern muss, annotieren Sie eine neue Klasse oder Schnittstelle mit @CustomTestApplication und übergeben Sie den Wert der Basisklasse, die von der generierten Hilt-Anwendung erweitert werden soll.

@CustomTestApplication generiert eine Application-Klasse, die zum Testen mit Hilt bereit ist und die Anwendung erweitert, die Sie als Parameter übergeben haben.

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

In diesem Beispiel generiert Hilt eine Application namens HiltTestApplication_Application, die die Klasse BaseApplication erweitert. Im Allgemeinen ist der Name der generierten Anwendung der Name der annotierten Klasse, an die _Application angehängt wird. Sie müssen die generierte Hilt-Testanwendung so einrichten, dass sie in Ihren instrumentierten Tests oder Robolectric-Tests ausgeführt wird, wie unter Testanwendung beschrieben.

Mehrere TestRule-Objekte in Ihrem instrumentierten Test

Wenn Sie andere TestRule-Objekte in Ihrem Test haben, gibt es mehrere Möglichkeiten, dafür zu sorgen, dass alle Regeln zusammenwirken.

Sie können die Regeln wie folgt zusammenfassen:

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.
}

Alternativ können Sie beide Regeln auf derselben Ebene verwenden, solange HiltAndroidRule zuerst ausgeführt wird. Geben Sie die Ausführungsreihenfolge mit dem Attribut order in der Annotation @Rule an. Dies funktioniert nur ab JUnit-Version 4.13:

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

launchFragmentInContainer aus der Bibliothek androidx.fragment:fragment-testing kann nicht mit „Hilt“ verwendet werden, da es auf einer Aktivität basiert, die nicht mit @AndroidEntryPoint annotiert ist.

Verwenden Sie stattdessen den Code launchFragmentInHiltContainer aus dem GitHub-Repository architecture-samples.

Verwenden Sie einen Einstiegspunkt, bevor die Singleton-Komponente verfügbar ist

Die Annotation @EarlyEntryPoint bietet eine Ausstiegsmöglichkeit, wenn ein Hilt-Einstiegspunkt erstellt werden muss, bevor die Singleton-Komponente in einem Treffertest verfügbar ist.

Weitere Informationen zu @EarlyEntryPoint finden Sie in der Hilt-Dokumentation.