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

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

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

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

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

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

Groovy

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


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

Kotlin

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


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

การตั้งค่าการทดสอบ 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 ในการทดสอบที่ใช้เครื่องมือ คุณต้องกำหนดค่า Test Runner ใหม่ ซึ่งจะทำให้ 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 ด้วย a fake โดยใช้ @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
}

หากต้องการเพิ่ม multibinding คุณสามารถใช้คำอธิบายประกอบ @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

โปรดใช้โค้ด launchFragmentInHiltContainer จากที่เก็บ architecture-samples GitHub แทน

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

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

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