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

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

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

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

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 จะแทรกทรัพยากร Dependency เหมือนกับที่ทำในโค้ดเวอร์ชันที่ใช้งานจริง การทดสอบด้วย Hilt ไม่จำเป็นต้องมีการบำรุงรักษาเนื่องจาก Hilt จะสร้างชุดคอมโพเนนต์ใหม่สำหรับการทดสอบแต่ละครั้งโดยอัตโนมัติ

การเพิ่มทรัพยากร Dependency สำหรับการทดสอบ

หากต้องการใช้ Hilt ในการทดสอบ ให้ใส่ทรัพยากร Dependency hilt-android-testing ในโปรเจ็กต์

ดึงดูด

dependencies {
    // For Robolectric tests.
    testImplementation 'com.google.dagger:hilt-android-testing:2.57.1'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.57.1'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.57.1'


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

Kotlin

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


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

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

คุณต้องใส่คำอธิบายประกอบ @HiltAndroidTest ในการทดสอบ UI ที่ใช้ Hilt คำอธิบายประกอบนี้มีหน้าที่สร้างคอมโพเนนต์ 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 ในการทดสอบที่ใช้เครื่องมือ instrumented tests คุณต้องกำหนดค่าตัวเรียกใช้การทดสอบใหม่ ซึ่งจะทำให้ 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 แบบเต็ม

ดึงดูด

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 ของโมดูล

ดึงดูด

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 ระบบจะแทรกทรัพยากร Dependency ปลอมในการทดสอบทั้งหมดในโฟลเดอร์นั้นแทน

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 และสร้างโมดูลทดสอบใหม่ในการทดสอบ

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

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 เพื่อเชื่อมโยงฟิลด์ในการทดสอบเข้ากับกราฟทรัพยากร Dependency ของ 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 ไม่ได้ เนื่องจากฟังก์ชันนี้ต้องอาศัยกิจกรรมที่ไม่ได้ใส่คำอธิบายประกอบ @AndroidEntryPoint

ให้ใช้โค้ดจากที่เก็บ GitHub แทนlaunchFragmentInHiltContainerarchitecture-samples

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

คำอธิบายประกอบ @EarlyEntryPoint มีไว้ให้ใช้ในกรณีที่ต้องสร้างจุดเริ่มต้น Hilt ก่อนที่คอมโพเนนต์ Singleton จะพร้อมใช้งานในการทดสอบ Hilt

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