使用Dagger 2进行Android单元测试

时间:2015-05-01 14:55:39

标签: java android unit-testing dagger-2

我有一个使用Dagger 2进行依赖注入的Android应用。我还使用最新的gradle构建工具,它允许构建变体进行单元测试,并使用一个用于仪器测试。我在我的应用程序中使用java.util.Random,我想模仿它进行测试。我测试的课程不使用任何Android内容,因此他们只是常规的Java课程。

在我的主要代码中,我在扩展Component类的类中定义了Application,但在单元测试中,我没有使用Application。我尝试定义测试ModuleComponent,但Dagger不会生成Component。我还尝试使用我在应用程序中定义的Component并在构建时交换Module,但应用程序Component没有inject Random 1}}我的测试类的方法。如何为测试提供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);
    }
}

测试模块:

lmwine <- lm(quality~fixed.acidity+volatile.acidity+citric.acid+residual.sugar+chlorides+
             free.sulfur.dioxide+total.sulfur.dioxide+density+pH+sulphates+alcohol,data=wineq)

summary(lm)

5 个答案:

答案 0 :(得分:25)

目前无法使用Dagger 2(从v2.0.0开始),但没有一些解决方法。你可以阅读它here

有关可能的解决方法的更多信息:

答案 1 :(得分:6)

你说:“你已经击中了头上的钉子:

  

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

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

1)创建Application类的测试版

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 test的 setup()方法中,在测试应用上设置此测试模块:

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

现在,您可以根据自己的需要自定义测试模块。

答案 2 :(得分:2)

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

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

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

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

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

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() {}

  ...
}

答案 3 :(得分:1)

如果您在Android上使用dagger2,则可以使用app flavor来提供模拟资源。

在这里查看模拟测试中的风味演示(没有匕首): 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

答案 4 :(得分:0)

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

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

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();
    }
}