匕首线切割图纸下载_使用Robolectric测试带有匕首注入依赖性的类

匕首线切割图纸下载

It is common for Android code to use dependency injection (DI). And one of the tenets of DI is to make code more testable. So it would follow that if your Android classes use DI, then they should be easily testable.

Android代码通常使用依赖项注入(DI)。 DI的宗旨之一就是使代码更具可测试性。 因此,如果您的Android类使用DI,那么它们应该易于测试。

That is true if your class uses constructor injection. You simply call the class’s constructor and provide implementations of its dependencies in the parameters to the constructor. Often, that means passing mocks of the dependencies so you can simulate various conditions in your unit test.

如果您的类使用构造函数注入,则为true。 您只需调用类的构造函数,并在构造函数的参数中提供其依赖项的实现。 通常,这意味着传递依赖项的模拟,以便您可以在单元测试中模拟各种条件。

But if your class uses field injection, then testing is a little tricker. In that case, you need to construct the object, and then somehow set the values of each injected field before you test any methods that use those fields.

但是,如果您的班级使用字段注入,那么测试就有些麻烦了。 在这种情况下,您需要构造对象,然后以某种方式设置每个注入字段的值,然后再测试使用这些字段的任何方法。

The real problem comes when your class uses field injection and you cannot control the creation lifecycle of the class. That is the case with several Android classes, like Activities and Views. The Android framework controls the creation of these. For example, a View is often created by inflating it from an XML file, meaning that the Android framework will not only control calling the View’s (default) constructor but will also call various lifecycle methods such as onFinishInflate(). And since we cannot specify custom constructors for injecting dependencies, we need to use field injection.

当您的类使用字段注入并且无法控制类的创建生命周期时,就会出现真正的问题。 几个Android类(例如“活动”和“视图”)就是这种情况。 Android框架控制着这些的创建。 例如,通常通过从XML文件onFinishInflate()视图来创建View,这意味着Android框架不仅会控制调用View的(默认)构造函数,还会调用各种生命周期方法,例如onFinishInflate() 。 而且由于我们无法指定用于注入依赖项的自定义构造函数,因此我们需要使用字段注入。

With Dagger, field injection works by annotating such fields with @Inject, and then the class with those fields will call the relevant injection method in the @Component object, passing itself in so that Dagger can set those fields on that object appropriately. The call looks something like this:

使用Dagger,通过使用@Inject注释此类字段来进行字段注入,然后包含这些字段的类将在@Component对象中调用相关的注入方法,并将其自身传入,以便Dagger可以在该对象上适当地设置这些字段。 呼叫看起来像这样:

class OurView {
...
init {
ourComponent.inject(this)
...
}
...
}

The corresponding method in the component interface is like this:

组件接口中的相应方法如下:

@Component
interface OurComponent {
...
// Note that Dagger does not care about the name of this method;
// we call it inject() per our own convention.
fun inject(ourView: OurView)
...
}

And often, that call will be done in the constructor or some other initialization block (e. g. an init block in Kotlin) or some initializing lifecycle method like onCreate() or onFinishInflate().

通常,该调用将在构造函数或其他初始化块(例如Kotlin中的init块)或某些初始化生命周期方法(例如onCreate()onFinishInflate()

If you are writing a unit test within Robolectric, you want to have control over how those injected field values are set. For example, if one of those fields is your Presenter, you might want to mock it and have it return errors for particular tests, or return different values to test how your View renders those. That means you want to override what myComponent.inject(myView) does. As such, it means that you need to be able to pass in a special implementation of your @Component to your class-under-test, so that you can have it inject whatever values you wish.

如果要在Robolectric中编写单元测试,则希望控制如何设置那些注入的字段值。 例如,如果这些字段之一是您的Presenter,则可能要对其进行模拟,并使其针对特定测试返回错误,或者返回不同的值以测试View如何呈现这些内容。 这意味着您要重写myComponent.inject(myView)功能。 因此,这意味着您需要能够将@Component的特殊实现传递给被测类,以便可以将其注入所需的任何值。

For our Android apps at Thumbtack, we have this exact situation. We have several View classes that have Dagger field injection. And almost all of them call the Component’s inject method within their init{} blocks (our code base is almost all Kotlin). For our particular case, our @Component is accessible as a field from within our Application object, so our View classes are able to call their injection methods in a manner similar to the following:

对于Thumbtack上的Android应用,我们有这种情况。 我们有几个具有Dagger字段注入的View类。 几乎所有人都在init{}块中调用了Component的inject方法(我们的代码库几乎都是Kotlin)。 对于我们的特殊情况,我们的@Component可作为Application对象中的字段进行访问,因此我们的View类能够以类似于以下方式的方式调用其注入方法:

class OurView {
...
init {
if (!isInEditMode) {
(context.applicationContext as OurApplication)
.appComponent
.inject(this)
}

...
}
...
}

So when we write a Robolectric test for this View, we subclass our Application class to provide our own implementation of Component, like so:

因此,当我们为此视图编写Robolectric测试时,我们将Application类子类化以提供我们自己的Component实现,如下所示:

object TestApplication : OurApplication() {
...
override val appComponent: OurComponent = mockk() // mockk.io
}

You will notice in the above that, rather than providing a subclass of our Component, we simply provide a mock object. It makes it easier than having to provide implementations for every inject method within that Component, which in our case is hundreds of methods.

您会在上面注意到,我们没有提供Component的子类,而是提供了一个模拟对象。 它比必须为该组件中的每个注入方法提供实现要容易得多,在我们的例子中,该方法有数百种方法。

More importantly, by making it a mock, each test we write can simply provide implementations of only those inject() methods that are needed. For OurView, all we need to do is provide an implementation of that inject() method to the mock, and we can have it set the View’s fields however we wish:

更重要的是,通过使其成为模拟,我们编写的每个测试都可以仅提供所需的那些inject()方法的实现。 对于OurView ,我们所需要做的就是为该模拟程序提供该inject()方法的实现,并且我们可以根据需要设置它设置View的字段:

@Config(
application = TestApplication::class,
... other config values here ...
)
@RunWith(RobolectricTestRunner::class)
class OurViewTest { @Before
fun setup() {
every { TestApplication.appComponent.inject(any<OurView>()) }
.answers {
with(firstArg<OurView>()) {
textFormatter = mockTextFormatter
clock = mockClock
logger = mockLogger
}
}

...
} @Test
fun ourTest() {
...
}
}

Often, a View may depend on another View that also has injected fields (for example, a child View). For that, all you have to do is provide another every{}.answers{} clause, but for that child View.

通常,一个视图可能依赖于另一个也已注入字段的视图(例如,子视图)。 为此,您只需要提供另一个every{}.answers{}子句即可,但要提供该子View。

We even wrote a helper method to make the call site syntax slightly more concise. It is an extension method that you call on the View’s class object. The argument is a lambda that runs on the instance of the View being constructed, so “this” refers to the OurView instance being constructed. Within this lambda, you assign the necessary values to the injected fields of your View. So now the above would be called as:

我们甚至编写了一个辅助方法,以使调用站点的语法更加简洁。 它是您在View的class对象上调用的扩展方法。 该参数是在正在构造的View实例上运行的lambda,因此“ this”是指正在构造的OurView实例。 在此lambda中,您将必要的值分配给View的注入字段。 因此,以上内容称为:

@Before
fun setUp() {
OurView::class.setupInjection {
textFormatter = mockTextFormatter
clock = mockClock
logger = mockLogger
}

...
}

The setupInjection() method is defined as:

setupInjection()方法定义为:

inline fun <reified B : Any, reified T : KClass<B>>
T.setupInjection(
crossinline callback: B.() -> Unit
) {
val method = appComponent.javaClass.getMethod(
"inject",
this.java
)
every {
method.invoke(appComponent, ofType(this@setupInjection))
} answers {
val injectionTarget = this.firstArg<B>()
callback.invoke(injectionTarget)
}
}

The syntax at the call site is now a little more concise.

现在,调用站点的语法更加简洁。

The above demonstrates how one can write unit tests for Android classes with field dependency injection when running them in an Android framework such as Robolectric. And really, the key is overriding the method that does the actual field injection and doing so before the object is initialized. For Dagger, it involves overriding the injection method within the relevant @Component class. Once that is done, you are able to reap the benefits of dependency injection and still be able to write focused unit tests for your Android components.

上面的示例演示了如何在诸如Robolectric之类的Android框架中运行带有字段依赖注入的Android类编写单元测试。 实际上,关键是要覆盖执行实际场注入的方法,并在初始化对象之前这样做。 对于Dagger,它涉及覆盖相关@Component类中的注入方法。 完成此操作后,您将能够获得依赖注入的好处,并且仍然能够为Android组件编写针对性的单元测试。

翻译自: https://medium.com/thumbtack-engineering/using-robolectric-to-test-classes-with-dagger-injected-dependencies-6c6967f17f6e

匕首线切割图纸下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值