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

ข้อดีอย่างหนึ่งของการใช้ Dependency Injection Framework เช่น Hilt คือ จะทำให้ทดสอบโค้ดได้ง่ายขึ้น

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

ฮิลต์ไม่จำเป็นสำหรับการทดสอบหน่วย เพราะเมื่อทดสอบคลาสที่ใช้ Constructor Injector คุณไม่จำเป็นต้องใช้ 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.44'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.44'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.44'


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

Kotlin

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


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

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

ตัวอย่างเช่น สมมติว่าโค้ดการผลิตของคุณประกาศการเชื่อมโยงสำหรับ 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 จากเวอร์ชันก่อนหน้า ให้เริ่มต้นด้วยการบอก ข้ามโมดูลเวอร์ชันที่ใช้งานจริงโดยใช้คำอธิบายประกอบ @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 คุณสามารถใช้ใน การทดสอบโรโมเลกุลมีดังนี้

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 ที่สร้างขึ้น แอปพลิเคชันเพื่อเรียกใช้ในการทดสอบแบบมีเครื่องวัด หรือ การทดสอบ Roolectric ตามที่อธิบายไว้ในการทดสอบ แอปพลิเคชัน

ออบเจ็กต์ 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.
}

เปิดFragmentInContainer

คุณไม่สามารถใช้ launchFragmentInContainer จาก ไลบรารี androidx.fragment:fragment-testing โดยใช้ Hilt เนื่องจากต้องอาศัย กิจกรรมที่ไม่มีคำอธิบายประกอบด้วย @AndroidEntryPoint

ใช้เมนู launchFragmentInHiltContainer จาก architecture-samples GitHub แทน

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

คำอธิบายประกอบ @EarlyEntryPoint มีทางลับเมื่อเข้าสู่ช่อง Hilt ต้องสร้างจุดก่อนที่คอมโพเนนต์ซิงเกิลตันจะพร้อมใช้งานใน การทดสอบ Hilt

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