คู่มือการทดสอบ 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
}

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