【问题标题】:Android Unit Tests with Dagger 2使用 Dagger 2 进行 Android 单元测试
【发布时间】:2015-07-11 10:12:37
【问题描述】:

我有一个使用 Dagger 2 进行依赖注入的 Android 应用。我还在使用最新的 gradle 构建工具,这些工具允许构建变体用于单元测试和一个用于仪器测试。我在我的应用程序中使用java.util.Random,我想模拟它进行测试。我正在测试的类不使用任何 Android 的东西,所以它们只是普通的 java 类。

在我的主代码中,我在扩展Application 类的类中定义了Component,但在单元测试中我没有使用Application。我尝试定义测试ModuleComponent,但Dagger 不会生成Component。我还尝试使用我在应用程序中定义的Component 并在构建它时交换Module,但应用程序的Component 没有inject 用于我的测试类的方法。如何提供Random 的模拟实现进行测试?

这里有一些示例代码:

应用:

public class PipeGameApplication extends Application {

    private PipeGame pipeGame;

    @Singleton
    @Component(modules = PipeGameModule.class)
    public interface PipeGame {
        void inject(BoardFragment boardFragment);
        void inject(ConveyorFragment conveyorFragment);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        pipeGame = DaggerPipeGameApplication_PipeGame.create();
    }

    public PipeGame component() {
        return pipeGame;
    }
}

模块:

@Module
public class PipeGameModule {

    @Provides
    @Singleton
    Random provideRandom() {
        return new Random();
    }
}

测试的基类:

public class BaseModelTest {

    PipeGameTest pipeGameTest;

    @Singleton
    @Component(modules = PipeGameTestModule.class)
    public interface PipeGameTest {
        void inject(BoardModelTest boardModelTest);
        void inject(ConveyorModelTest conveyorModelTest);
    }

    @Before
    public void setUp() {
        pipeGameTest = DaggerBaseModelTest_PipeGameTest.create(); // Doesn't work
    }

    public PipeGameTest component() {
        return pipeGameTest;
    }
}

或:

public class BaseModelTest {

    PipeGameApplication.PipeGame pipeGameTest;

    // This works if I make the test module extend
    // the prod module, but it can't inject my test classes
    @Before
    public void setUp() {
        pipeGameTest = DaggerPipeGameApplication_PipeGame.builder().pipeGameModule(new PipeGameModuleTest()).build();
    }

    public PipeGameApplication.PipeGame component() {
        return pipeGameTest;
    }
}

测试模块:

@Module
public class PipeGameTestModule {

    @Provides
    @Singleton
    Random provideRandom() {
        return mock(Random.class);
    }
}

【问题讨论】:

标签: java android unit-testing dagger-2


【解决方案1】:

如果没有一些变通方法,目前使用 Dagger 2(从 v2.0.0 开始)是不可能的。你可以阅读它here

更多关于可能的解决方法:

【讨论】:

  • 嗨@tomrozb。您能否提供有关此问题的更新答案或文章的建议?谢谢。
【解决方案2】:

在我看来,您可以从不同的角度来看待这个问题。您将能够轻松地对您的类进行单元测试,而不是依赖 Dagger 来构建被测构造类,并将其模拟依赖项注入其中。

我的意思是,在测试设置中你可以:

  • 模拟被测类的依赖关系
  • 使用模拟的依赖项手动构建被测类

我们不需要测试依赖项是否被正确注入,因为 Dagger 在编译期间会验证依赖关系图的正确性。因此,任何此类错误都会因编译失败而报告。这就是为什么在 setup 方法中手动创建被测类应该是可以接受的。

在被测类中使用构造函数注入依赖的代码示例:

public class BoardModelTest {

  private BoardModel boardModel;
  private Random random;

  @Before
  public void setUp() {
    random = mock(Random.class);
    boardModel = new BoardModel(random);
  }

  @Test
  ...
}

public class BoardModel {
  private Random random;

  @Inject
  public BoardModel(Random random) {
    this.random = random;
  }

  ...
}

使用被测类中的字段注入依赖项的代码示例(如果BoardModel由框架构造):

public class BoardModelTest {

  private BoardModel boardModel;
  private Random random;

  @Before
  public void setUp() {
    random = mock(Random.class);
    boardModel = new BoardModel();
    boardModel.random = random;
  }

  @Test
  ...
}

public class BoardModel {
  @Inject
  Random random;

  public BoardModel() {}

  ...
}

【讨论】:

  • 这在我的情况下不会真正起作用,因为我目前注入了很多依赖项,但这肯定适用于更简单的情况,如果你能做到这一点,我绝对认为这是正确的方法方式。
  • 如果你不使用 Dagger,为什么要使用 @Inject Random random;
【解决方案3】:

如果您在 Android 中使用 dagger2,则可以使用应用风格来提供模拟资源。

在此处查看模拟测试中的风味演示(不带匕首): https://www.youtube.com/watch?v=vdasFFfXKOY

这个代码库有一个例子: https://github.com/googlecodelabs/android-testing

在你的 /src/prod/com/yourcompany/Component.java 您提供生产组件。

在你的 /src/mock/com/yourcompany/Component.java 你提供你的模拟组件。

这允许您创建应用程序的构建,无论是否使用模拟。 它还允许并行开发(一个团队的后端,另一个团队的前端应用程序),您可以模拟直到 api 方法可用。

我的 gradle 命令看起来如何(它是一个 Makefile):

install_mock:
    ./gradlew installMockDebug

install:
    ./gradlew installProdDebug

test_unit:
    ./gradlew testMockDebugUnitTest

test_integration_mock:
    ./gradlew connectedMockDebugAndroidTest

test_integration_prod:
    ./gradlew connectedProdDebugAndroidTest

【讨论】:

  • 那个 repo 没有展示 Dagger 2 组件。你是如何在你的项目中模拟它们的?
【解决方案4】:

你说得一针见血:

应用程序的组件没有我的测试类的注入方法

所以,为了解决这个问题,我们可以为您的 Application 类制作一个测试版本。然后我们可以有你的模块的测试版本。为了让它在测试中运行,我们可以使用 Robolectric。

1) 创建应用程序类的测试版本

public class TestPipeGameApp extends PipeGameApp {
    private PipeGameModule pipeGameModule;

    @Override protected PipeGameModule getPipeGameModule() {
        if (pipeGameModule == null) {
            return super.pipeGameModule();
        }
        return pipeGameModule;
    }

    public void setPipeGameModule(PipeGameModule pipeGameModule) {
        this.pipeGameModule = pipeGameModule;
        initComponent();
    }}

2) 你原来的 Application 类需要有 initComponent()pipeGameModule() 方法

public class PipeGameApp extends Application {
    protected void initComponent() {
        DaggerPipeGameComponent.builder()
            .pipeGameModule(getPipeGameModule())
            .build();
    }

    protected PipeGameModule pipeGameModule() {
        return new PipeGameModule(this);
    }}

3) 您的 PipeGameTestModule 应该使用构造函数扩展生产模块:

public class PipeGameTestModule extends PipeGameModule {
    public PipeGameTestModule(Application app) {
        super(app);
    }}

4) 现在,在您的 junit 测试的 setup() 方法中,在您的测试应用上设置此测试模块:

@Before
public void setup() {
    TestPipeGameApp app = (TestPipeGameApp) RuntimeEnvironment.application;
    PipeGameTestModule module = new PipeGameTestModule(app);
    app.setPipeGameModule(module);
}

现在您可以按照自己的意愿自定义测试模块。

【讨论】:

  • RuntimeEvironment 是什么意思?
  • 伊戈尔太好了,谢谢。我只需要更多信息:这是如何注入测试类实例的?
  • 没关系,我只需要在我的组件中声明一个新的inject 方法并从我的测试的@Before 方法中调用它。对不起愚蠢的问题! :)
【解决方案5】:

我实际上遇到了同样的问题,并找到了一个非常简单的解决方案。 这不是我认为的最佳解决方案,但它会解决您的问题。

在您的应用模块中创建一个类似的类:

public class ActivityTest<T extends ViewModelBase> {

    @Inject
    public T vm;
}

然后,在您的 AppComponent 中添加:

void inject(ActivityTest<LoginFragmentVM> activityTest);

然后你就可以在你的测试类中注入它了。

 public class HelloWorldEspressoTest extends ActivityTest<LoginFragmentVM> {

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class);

    @Test
    public void listGoesOverTheFold() throws InterruptedException {
        App.getComponent().inject(this);
        vm.email.set("1234");
        closeSoftKeyboard();
    }
}

【讨论】:

  • 什么是ViewModelBase
猜你喜欢
  • 2016-11-13
  • 2019-01-22
  • 2020-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-07
  • 2020-02-22
  • 1970-01-01
相关资源
最近更新 更多