คู่มือการทดสอบ Hilt

ประโยชน์อย่างหนึ่งของการใช้เฟรมเวิร์ก Dependency Injection เช่น Hilt คือช่วยให้การทดสอบโค้ดง่ายขึ้น

การทดสอบ 1 หน่วย

คุณไม่จำเป็นต้องใช้ Hilt ในการทดสอบหน่วย เนื่องจากเมื่อทดสอบคลาสที่ใช้การฉีดคอนสตรัคเตอร์ คุณไม่จำเป็นต้องใช้ Hilt เพื่อสร้างอินสแตนซ์ของคลาสนั้น แต่คุณสามารถเรียกคอนสตรัคเตอร์ของคลาสโดยตรงได้โดยส่งพารามิเตอร์ที่จำลองหรือจำลองขึ้นมา เช่นเดียวกับในกรณีที่ไม่ได้กำกับเนื้อหาคอนสตรัคเตอร์

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

การทดสอบจากต้นทางถึงปลายทาง

สําหรับการทดสอบการผสานรวม Hilt จะแทรกการอ้างอิงเช่นเดียวกับในโค้ดเวอร์ชันที่ใช้งานจริง การทดสอบด้วย Hilt ไม่จำเป็นต้องมีการบำรุงรักษาเนื่องจาก Hilt จะสร้างคอมโพเนนต์ชุดใหม่สําหรับการทดสอบแต่ละครั้งโดยอัตโนมัติ

การเพิ่มการพึ่งพาการทดสอบ

หากต้องการใช้ Hilt ในการทดสอบ ให้รวมข้อกําหนด hilt-android-testing ไว้ในโปรเจ็กต์

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

การตั้งค่าการทดสอบ UI

คุณต้องกำกับเนื้อหาการทดสอบ UI ที่ใช้ Hilt ด้วย @HiltAndroidTest คําอธิบายประกอบนี้มีหน้าที่สร้างคอมโพเนนต์ Hilt สําหรับการทดสอบแต่ละครั้ง

นอกจากนี้ คุณต้องเพิ่ม HiltAndroidRule ลงในคลาสทดสอบด้วย โดยจะจัดการสถานะของคอมโพเนนต์และใช้เพื่อดําเนินการแทรกในทดสอบ

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

ถัดไป การทดสอบจะต้องทราบเกี่ยวกับคลาส Application ที่ Hilt สร้างขึ้นให้คุณโดยอัตโนมัติ

แอปพลิเคชันทดสอบ

คุณต้องทำการทดสอบที่มีเครื่องมือวัดผลซึ่งใช้ Hilt ในApplicationออบเจ็กต์ที่รองรับ Hilt ไลบรารีมี HiltTestApplication ไว้สําหรับใช้ในการทดสอบ หากการทดสอบต้องใช้แอปพลิเคชันพื้นฐานอื่น โปรดดูแอปพลิเคชันที่กำหนดเองสำหรับการทดสอบ

คุณต้องตั้งค่าแอปพลิเคชันทดสอบให้ทำงานในการทดสอบที่มีเครื่องมือวัดหรือการทดสอบ Robolectric วิธีการต่อไปนี้ไม่ได้เจาะจงสำหรับ Hilt แต่เป็นแนวทางทั่วไปเกี่ยวกับวิธีระบุแอปพลิเคชันที่กำหนดเองให้ทำงานในการทดสอบ

ตั้งค่าแอปพลิเคชันทดสอบในการทดสอบที่มีเครื่องควบคุม

หากต้องการใช้แอปพลิเคชันทดสอบ Hilt ในการทดสอบที่มีเครื่องมือวัด คุณต้องกำหนดค่าโปรแกรมรันทดสอบใหม่ ซึ่งจะทำให้ Hilt ทำงานกับการทดสอบที่มีเครื่องมือวัดผลทั้งหมดในโปรเจ็กต์ ทําตามขั้นตอนต่อไปนี้

  1. สร้างคลาสที่กำหนดเองซึ่งขยายจาก AndroidJUnitRunner ในโฟลเดอร์ androidTest
  2. ลบล้างฟังก์ชัน newApplication และส่งชื่อแอปพลิเคชันทดสอบ 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);
  }
}

ถัดไป ให้กําหนดค่าโปรแกรมรันทดสอบนี้ในไฟล์ Gradle ตามที่อธิบายไว้ในคู่มือการทดสอบหน่วยที่มีเครื่องมือวัด ตรวจสอบว่าคุณใช้ classpath แบบสมบูรณ์

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"
    }
}
ตั้งค่าแอปพลิเคชันทดสอบในการทดสอบ Robolectric

หากใช้ Robolectric เพื่อทดสอบเลเยอร์ UI คุณสามารถระบุแอปพลิเคชันที่จะใช้ในไฟล์ robolectric.properties ได้โดยทำดังนี้

application = dagger.hilt.android.testing.HiltTestApplication

หรือจะกำหนดค่าแอปพลิเคชันในแต่ละการทดสอบแยกกันก็ได้โดยใช้คำอธิบายประกอบ @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.
}

หากคุณใช้ปลั๊กอิน Android Gradle เวอร์ชันต่ำกว่า 4.2 ให้เปิดใช้การเปลี่ยนรูปแบบคลาส @AndroidEntryPoint ในการทดสอบยูนิตในเครื่องโดยใช้การกำหนดค่าต่อไปนี้ในไฟล์ build.gradle ของโมดูล

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

ข้อมูลเพิ่มเติมเกี่ยวกับ enableTransformForLocalTests ในเอกสารประกอบของ Hilt

การทดสอบฟีเจอร์

เมื่อ Hilt พร้อมใช้งานในการทดสอบแล้ว คุณจะใช้ฟีเจอร์ต่างๆ เพื่อปรับแต่งกระบวนการทดสอบได้

แทรกประเภทในการทดสอบ

หากต้องการแทรกประเภทลงในการทดสอบ ให้ใช้ @Inject สำหรับการแทรกช่อง หากต้องการบอก Hilt ให้ป้อนข้อมูลในช่อง @Inject ให้เรียกใช้ hiltRule.inject()

ดูตัวอย่างการทดสอบที่มีเครื่องมือวัดต่อไปนี้

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

แทนที่การเชื่อมโยง

หากต้องการแทรกอินสแตนซ์จำลองหรือจําลองของ Dependency คุณต้องบอก Hilt ว่าอย่าใช้การเชื่อมโยงที่ใช้ในโค้ดเวอร์ชันที่ใช้งานจริง และให้ใช้การเชื่อมโยงอื่นแทน หากต้องการแทนที่การเชื่อมโยง คุณต้องแทนที่โมดูลที่มีการเชื่อมโยงด้วยโมดูลทดสอบที่มีการเชื่อมโยงที่ต้องการใช้ในการทดสอบ

ตัวอย่างเช่น สมมติว่าโค้ดเวอร์ชันที่ใช้งานจริงของคุณประกาศการเชื่อมโยงสําหรับ 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
  );
}

หากต้องการแทนที่การเชื่อมโยง AnalyticsService ในการทดสอบ ให้สร้างโมดูล Hilt ใหม่ในโฟลเดอร์ test หรือ androidTest ที่มี Dependency จำลอง และกำกับเนื้อหาด้วย @TestInstallIn การทดสอบทั้งหมดในโฟลเดอร์นั้นจะมีการแทรกการพึ่งพาจำลองแทน

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

แทนที่การเชื่อมโยงในการทดสอบครั้งเดียว

หากต้องการแทนที่การเชื่อมโยงในการทดสอบเดียวแทนการทดสอบทั้งหมด ให้ถอนการติดตั้งข้อบังคับของ Hilt ออกจากการทดสอบโดยใช้การกำกับเนื้อหา @UninstallModules และสร้างข้อบังคับการทดสอบใหม่ภายในการทดสอบ

จากตัวอย่าง AnalyticsService จากเวอร์ชันก่อนหน้า ให้เริ่มด้วยการบอก Hilt ให้ละเว้นโมดูลเวอร์ชันที่ใช้งานจริงโดยใช้คำอธิบายประกอบ @UninstallModules ในคลาสทดสอบ

Kotlin

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

Java

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

ถัดไป คุณต้องเปลี่ยนการเชื่อมโยง สร้างโมดูลใหม่ภายในคลาสทดสอบซึ่งกำหนดการเชื่อมโยงการทดสอบ

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

การดำเนินการนี้จะแทนที่การเชื่อมโยงสำหรับคลาสทดสอบรายการเดียวเท่านั้น หากต้องการแทนที่การเชื่อมโยงสำหรับคลาสทดสอบทั้งหมด ให้ใช้คำอธิบายประกอบ @TestInstallIn จากส่วนด้านบน หรือจะใส่การเชื่อมโยงการทดสอบในโมดูล test สำหรับทดสอบด้วย Robolectric หรือในโมดูล androidTest สําหรับการทดสอบที่มีเครื่องมือวัดก็ได้ เราขอแนะนำให้ใช้ @TestInstallIn เมื่อเป็นไปได้

การเชื่อมโยงค่าใหม่

ใช้การกำกับเนื้อหา @BindValue เพื่อเชื่อมโยงช่องในการทดสอบกับกราฟความขึ้นต่อกันของ Hilt ได้อย่างง่ายดาย กำกับเนื้อหาในช่องด้วย @BindValue แล้วระบบจะเชื่อมโยงช่องนั้นภายใต้ประเภทช่องที่ประกาศไว้ด้วยตัวคําจํากัดที่มีอยู่สําหรับช่องนั้น

ในตัวอย่าง AnalyticsService คุณสามารถแทนที่ AnalyticsService ด้วยค่าสมมติได้โดยใช้ @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();

  ...
}

ซึ่งจะลดความซับซ้อนทั้งการแทนที่การเชื่อมโยงและการอ้างอิงการเชื่อมโยงในการทดสอบ โดยให้คุณทําทั้ง 2 อย่างพร้อมกันได้

@BindValue ทำงานร่วมกับตัวระบุและคำอธิบายประกอบการทดสอบอื่นๆ ตัวอย่างเช่น หากใช้ไลบรารีการทดสอบ เช่น Mockito คุณจะใช้ไลบรารีดังกล่าวในการทดสอบ Robolectric ได้โดยทำดังนี้

Kotlin

...
class SettingsActivityTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Java

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

  // Robolectric tests here
}

หากต้องการเพิ่มการเชื่อมโยงหลายรายการ ให้ใช้คำอธิบายประกอบ @BindValueIntoSet และ @BindValueIntoMap แทน @BindValue @BindValueIntoMap กำหนดให้คุณใส่คำอธิบายประกอบในช่องด้วยคำอธิบายประกอบคีย์แผนที่

กรณีพิเศษ

นอกจากนี้ Hilt ยังมีฟีเจอร์ที่รองรับกรณีการใช้งานที่ไม่ใช่มาตรฐานด้วย

แอปพลิเคชันที่กําหนดเองสําหรับการทดสอบ

หากใช้ HiltTestApplication ไม่ได้เนื่องจากแอปพลิเคชันทดสอบต้องขยายแอปพลิเคชันอื่น ให้ใส่คำอธิบายประกอบคลาสหรืออินเทอร์เฟซใหม่ด้วย @CustomTestApplication โดยส่งค่าของคลาสพื้นฐานที่คุณต้องการให้แอปพลิเคชัน Hilt ที่สร้างขึ้นขยาย

@CustomTestApplication จะสร้างคลาส Application ที่พร้อมสำหรับการทดสอบด้วย Hilt ซึ่งขยายแอปพลิเคชันที่คุณส่งผ่านเป็นพารามิเตอร์

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

ในตัวอย่าง Hilt จะสร้าง Application ชื่อ HiltTestApplication_Application ที่ขยายคลาส BaseApplication โดยทั่วไปแล้ว ชื่อของแอปพลิเคชันที่สร้างขึ้นคือชื่อของคลาสที่มีคำอธิบายประกอบต่อท้ายด้วย _Application คุณต้องตั้งค่าแอปพลิเคชันทดสอบ Hilt ที่สร้างขึ้นให้ทำงานในการทดสอบที่มีเครื่องมือวัดหรือการทดสอบ Robolectric ตามที่อธิบายไว้ในแอปพลิเคชันทดสอบ

ออบเจ็กต์ TestRule หลายรายการในการทดสอบที่มีเครื่องควบคุม

หากมีออบเจ็กต์ TestRule อื่นๆ ในการทดสอบ คุณมีหลายวิธีในการตรวจสอบว่ากฎทั้งหมดทํางานร่วมกัน

คุณสามารถรวมกฎเข้าด้วยกันได้ดังนี้

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

หรือจะใช้ทั้ง 2 กฎในระดับเดียวกันก็ได้ ตราบใดที่HiltAndroidRuleจะทำงานก่อน ระบุลําดับการดําเนินการโดยใช้แอตทริบิวต์ order ในการกำกับเนื้อหา @Rule การดำเนินการนี้จะใช้ได้ใน JUnit เวอร์ชัน 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 จากห้องสมุด androidx.fragment:fragment-testing กับ Hilt ได้ เนื่องจาก Hilt อาศัยกิจกรรมที่ไม่ได้กำกับเนื้อหาด้วย @AndroidEntryPoint

ให้ใช้โค้ดจากที่เก็บ GitHub ของ architecture-samples แทน launchFragmentInHiltContainer

ใช้จุดแรกเข้าก่อนที่คอมโพเนนต์แบบ Singleton จะพร้อมใช้งาน

คําอธิบายประกอบ @EarlyEntryPoint จะเป็นทางออกเมื่อต้องสร้าง Entry Point ของ Hilt ก่อนเพื่อให้คอมโพเนนต์แบบ Singleton พร้อมใช้งานในการทดสอบ Hilt

ดูข้อมูลเพิ่มเติมเกี่ยวกับ @EarlyEntryPoint ในเอกสารประกอบของ Hilt