Leitfaden für Hilt-Tests

Einer der Vorteile von Dependency Injection-Frameworks wie Hilt ist, dass sie das Testen von Code erleichtern.

Einheitentests

Für Unittests ist kein „Hilt“ erforderlich, da beim Testen einer Klasse, die Konstruktor eingeschleust haben, müssen Sie diese Klasse nicht mit Hilt instanziieren. Stattdessen können Sie einen Klassenkonstruktor direkt aufrufen, indem Sie gefälschte oder Mock-Abhängigkeiten übergeben, genau wie wenn der Konstruktor nicht annotiert wäre:

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

Für Integrationstests fügt Hilt Abhängigkeiten ein, wie es auch in Ihrem Produktionscode der Fall wäre. Tests mit Hilt sind wartungsfrei, da Hilt automatisch generiert für jeden Test einen neuen Satz von Komponenten.

Testabhängigkeiten hinzufügen

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

Cool

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

UI-Test einrichten

Alle UI-Tests, in denen Hilt verwendet wird, müssen mit @HiltAndroidTest annotiert werden. Dieses -Annotation ist für das Generieren der Hilt-Komponenten für jeden Test verantwortlich.

Außerdem musst du der Testklasse das HiltAndroidRule hinzufügen. Es verwaltet die Komponenten und wird verwendet, um in Ihren Test einzuschleusen:

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 Application-Klasse haben, die Hilt automatisch für Sie erstellt.

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, lesen Sie den Abschnitt Benutzerdefinierte Anwendung für Tests.

Sie müssen Ihre Testanwendung so einrichten, dass sie in Ihren instrumentierten Tests oder Robolectric-Tests ausgeführt wird. Die folgenden Anweisungen sind nicht speziell für Hilt, aber sind allgemeine Richtlinien zur Angabe eines benutzerdefinierten die in Tests ausgeführt werden soll.

Testanwendung in instrumentierten Tests festlegen

So verwenden Sie die Hilt-Testanwendung in instrumentierten Tests müssen Sie einen neuen Test-Runner konfigurieren. Daher funktioniert Hilt für alle instrumentierten Tests in Ihrem Projekt. Perform führen Sie die folgenden Schritte aus:

  1. Benutzerdefinierte Klasse erstellen, die AndroidJUnitRunner Zoll Ordner androidTest.
  2. Überschreiben Sie die Funktion newApplication und übergeben Sie den Namen des 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 Ihrer Gradle-Datei, wie in der Instrumented Unit Test . Verwenden Sie den vollständigen Pfad:

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 angeben, welche Anwendung zur Verwendung in der Datei robolectric.properties:

application = dagger.hilt.android.testing.HiltTestApplication

Alternativ können Sie die Anwendung bei jedem Test einzeln konfigurieren, indem Sie mit der Annotation @Config von 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.
}

Wenn Sie eine ältere Version des Android-Gradle-Plug-ins als 4.2 verwenden, aktivieren Sie Transformieren von @AndroidEntryPoint-Klassen in lokalen Einheitentests durch Anwenden der folgende Konfiguration in der build.gradle-Datei Ihres Moduls:

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Weitere Informationen zu enableTransformForLocalTests im Hilt Dokumentation.

Testfunktionen

Sobald Hilt für Ihre Tests einsatzbereit ist, können Sie verschiedene Funktionen nutzen, Anpassung des Testprozesses.

Typen in Tests einschleusen

Verwende @Inject für die Feldeinschleusung, um Typen in einen Test einzufügen. Um Hilt zu bitten, @Inject-Felder mit Daten füllen, rufen Sie hiltRule.inject() auf.

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 gefälschte oder simulierte Instanz einer Abhängigkeit einschleusen möchten, müssen Sie Hilt anweisen, die Bindung, die im Produktionscode verwendet wird, nicht zu verwenden, sondern stattdessen eine andere. Wenn Sie eine Bindung ersetzen möchten, müssen Sie das Modul, das die Bindung enthält, durch ein Testmodul ersetzen, das die Bindungen enthält, die Sie im Test verwenden möchten.

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

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

Erstellen Sie zum Ersetzen der AnalyticsService-Bindung in Tests ein neues Hilt-Modul in den Ordner test oder androidTest mit der fiktiven Abhängigkeit und annotieren mit @TestInstallIn. Stattdessen wird allen Tests in diesem Ordner die gefälschte Abhängigkeit hinzugefügt.

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 einen „Hilt“ Modul aus einem Test mit der Annotation @UninstallModules und erstellen Sie ein neues Testmoduls innerhalb des Tests.

Folgen Sie dem AnalyticsService-Beispiel aus der vorherigen Version. Hilt, um das Produktionsmodul mithilfe der Annotation @UninstallModules zu ignorieren 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. Neues Modul in der Testklasse erstellen der 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 Bindung für alle Testklassen verwenden, verwenden Sie die Annotation @TestInstallIn aus der oben. Alternativ können Sie die Testbindung in das Modul test einfügen. für Robolectric-Tests und im androidTest-Modul für instrumentierte Tests. Es wird empfohlen, nach Möglichkeit @TestInstallIn zu verwenden.

Neue Werte binden

Mit der Annotation @BindValue kannst du Felder in deinem Test ganz einfach in „Hilt“ binden. Abhängigkeitsdiagramm. Wenn Sie ein Feld mit @BindValue annotieren, wird es unter dem angegebenen Feldtyp mit allen für dieses Feld vorhandenen Qualifikationen gebunden.

Im Beispiel für AnalyticsService können Sie AnalyticsService durch einen gefälschten Wert ersetzen, indem Sie @BindValue verwenden:

Kotlin

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

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

  ...
}

Java

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

  @BindValue AnalyticsService analyticsService = FakeAnalyticsService();

  ...
}

So können Sie sowohl eine Bindung ersetzen als auch auf eine Bindung in Ihrem Test verweisen.

@BindValue funktioniert mit Qualifizierern und anderen Testannotationen. Wenn Sie beispielsweise Testbibliotheken wie Mockito verwenden, können Sie sie so in einem Robolectric-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 verwenden. von @BindValue. @BindValueIntoMap erfordert, dass Sie das Feld ebenfalls annotieren mit einer Map Key-Anmerkung.

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 um eine andere Anwendung zu erweitern, eine neue Klasse oder Schnittstelle mit @CustomTestApplication und übergibt den Wert der Basisklasse, für die der die Hilt-Anwendung erweitert werden soll.

@CustomTestApplication generiert eine Application-Klasse, die zum Testen bereit ist mit Hilt, das die als Parameter übergebene Anwendung erweitert.

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. In allgemein ist der Name der generierten Anwendung der Name der annotierten Klasse mit angehängtem _Application. 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, dass alle Regeln ineinandergreifen.

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 mithilfe der order-Attribut in der Anmerkung @Rule. Dies funktioniert nur in der JUnit-Version 4.13 oder höher:

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 kann nicht aus dem androidx.fragment:fragment-testing Bibliothek in Hilt, weil sie auf einem Aktivität, die nicht mit @AndroidEntryPoint gekennzeichnet ist.

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

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

Die Anmerkung @EarlyEntryPoint bietet eine Ausstiegsmöglichkeit, wenn ein „Hilt“-Eintrag muss erstellt werden, bevor die Singleton-Komponente Hilt-Test.

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