Przewodnik po testowaniu śladu

Jedną z zalet korzystania z ramek wstrzykiwania zależności, takich jak Hilt, jest ułatwienie testowania kodu.

Testy jednostkowe

Hilt nie jest potrzebny do testów jednostkowych, ponieważ podczas testowania klasy, która korzysta z wstrzyknięcia konstruktora, nie musisz tworzyć instancji tej klasy za pomocą Hilta. Zamiast tego możesz bezpośrednio wywołać konstruktor klasy, przekazując fałszywe lub mockowane zależności, tak jak w przypadku konstruktora bez 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 tak samo jak w środowisku produkcyjnym w kodzie. Testowanie za pomocą Hilt nie wymaga konserwacji, ponieważ Hilt automatycznie generuje nowy zestaw komponentów dla każdego testu.

Dodawanie zależności testowania

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

Groovy

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

Konfiguracja testu interfejsu

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

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

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 obsługującą Hilt generowane automatycznie.

Testuj aplikację

Musisz wykonać testy zinstrumentowane 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 artykułem Niestandardowa aplikacja do testów.

Należy ustawić aplikację testową tak, aby uruchamiała się w Testy lub Robolectric Nie można wykonywać tych czynności: dotyczące Hilt, ale zawierają ogólne wskazówki dotyczące do testowania.

Ustawienie aplikacji testowej w testach instrumentowanych

Korzystanie z aplikacji testowej Hilt w instrumentach testów, musisz skonfigurować nowy moduł uruchamiający test. Dzięki temu Hilt będzie działać we wszystkich testach zinstrumentowanych w projekcie. Wykonaj te kroki:

  1. Tworzenie niestandardowej klasy z rozszerzeniem AndroidJUnitRunner in folder androidTest.
  2. Zastąp funkcję newApplication i przekaż nazwę wygenerowanego Aplikacja testowa 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 tego testującego w pliku Gradle zgodnie z opisem w przewodniku po testach jednostkowych z użyciem instrumentacji. Upewnij się, użyj 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"
    }
}
Ustaw aplikację testową w testach Robolectric

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

application = dagger.hilt.android.testing.HiltTestApplication

Możesz też skonfigurować aplikację w każdym teście pojedynczo, za pomocą 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łcenie klas @AndroidEntryPoint w testy jednostkowe lokalnej przez zastosowanie tę konfigurację w pliku build.gradle modułu:

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Więcej informacji o enableTransformForLocalTests znajdziesz w dokumentacji Hiltu.

Funkcje testowe

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

Wstawianie typów w testach

Aby wstrzykiwać typy do testu, używaj @Inject do wstrzykiwania pól. Aby nakazywać Hiltowi wypełnij pola @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łszywy lub symulowany egzemplarz zależności, musisz powiedzieć Hiltowi, aby nie używał powiązania, którego używał w kodzie produkcyjnym, a zamiast tego użył innego. Aby zastąpić powiązanie, musisz zastąpić moduł, który zawiera powiązanie z modułem testowym, który zawiera wybrane powiązania. których można użyć 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ć w testach powiązanie AnalyticsService, utwórz nowy moduł Hilt w folderze test lub androidTest z fałszywą zależnością i opatrz go adnotacją @TestInstallIn. Wszystkie testy w tym folderze wstrzykiwane są przy użyciu fałszywych 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 pojedynczym teście zamiast we wszystkich testach, odinstaluj moduł Hilta z testu za pomocą adnotacji @UninstallModules i utwórz nowy moduł testu w ramach testu.

Korzystając z przykładu AnalyticsService z poprzedniej wersji, zacznij od polecenia Jego kliknięcie powoduje zignorowanie modułu produkcyjnego za pomocą adnotacji @UninstallModules na zajęciach testowych:

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 nowy moduł w klasie testu, 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
    );
  }
  ...
}

Spowoduje to zastąpienie tylko powiązania dla jednej klasy testów. Jeśli chcesz zastąpić dla wszystkich klas testowych, użyj adnotacji @TestInstallIn z tagu powyżej. Możesz też umieścić testowe powiązanie w module test w przypadku testów Robolectric, a w module androidTest – do testów instrumentowanych. Zalecamy, aby w miarę możliwości używać pola @TestInstallIn.

Powiązanie nowych wartości

Użyj adnotacji @BindValue, aby łatwo wiązać pola w teście z Hilt, wykres zależności. Dodaj adnotację do pola @BindValue, aby je objąć zadeklarowany typ pola ze wszystkimi kwalifikatorami, które są w nim obecne.

W przykładzie AnalyticsService możesz zamienić element AnalyticsService na fałszywka, 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();

  ...
}

Dzięki temu możesz zastąpić powiązanie i odwoływać się do niego w testach jednocześnie.

@BindValue działa z kwalifikatorami i innymi adnotacjami testowymi. Przykład: jeśli używasz bibliotek testowych takich jak Mockito, możesz go użyć w Jak wygląda test Roboelektryczny:

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 musisz dodać wiązanie wieloelementowe, możesz użyć adnotacji @BindValueIntoSet@BindValueIntoMap zamiast @BindValue. @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ć usługi HiltTestApplication, ponieważ aplikacja testowa musi: rozszerzania innej aplikacji, dodawania adnotacji do nowej klasy lub interfejsu @CustomTestApplication, przekazując wartość klasy bazowej, którą chcesz ustawić Wygenerowano aplikację Hilt do rozszerzenia.

@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 przykładzie Hilt generuje pole Application o nazwie HiltTestApplication_Application rozszerzający klasę BaseApplication. W ogólny, nazwa wygenerowanej aplikacji jest nazwą z adnotacjami zajęcia z dodanym elementem _Application. Musisz ustawić wygenerowany test Hilt aplikację do uruchomienia w testach instrumentalnych lub Testy Robolectric, zgodnie z opisem w sekcji Testy aplikacji.

Wiele obiektów TestRule w teście instrumentalnym

Jeśli masz w teście inne obiekty TestRule, możesz to zrobić na kilka sposobów. aby upewnić się, że wszystkie reguły będą ze sobą działać.

Reguły możesz owinąć 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 Komponent HiltAndroidRule jest uruchamiany jako pierwszy. Określ kolejność wykonywania za pomocą atrybutu order w adnotacji @Rule. Działa to tylko w wersji JUnit 4.13 lub nowszy:

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ć funkcji launchFragmentInContainer z biblioteki androidx.fragment:fragment-testing w Hilt, ponieważ korzysta ona z aktywności, która nie jest opatrzona adnotacjami @AndroidEntryPoint.

Użyj launchFragmentInHiltContainer z interfejsu architecture-samples GitHub .

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

Adnotacja @EarlyEntryPoint zapewnia dostęp awaryjny, gdy wpis Hilt aby komponent singleton był dostępny w Test z ręką.

Więcej informacji o @EarlyEntryPoint w Dokumentacja Hilt