java 使用 Dagger 2 进行 Android 单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29989245/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 16:15:08  来源:igfitidea点击:

Android Unit Tests with Dagger 2

javaandroidunit-testingdagger-2

提问by Pikaling

I have an Android app that uses Dagger 2 for dependency injection. I am also using the latest gradle build tools that allow a build variant for unit testing and one for instrumentation tests. I am using java.util.Randomin my app, and I want to mock this for testing. The classes I'm testing don't use any Android stuff, so they're just regular java classes.

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

In my main code I define a Componentin a class that extends the Applicationclass, but in the unit tests I'm not using an Application. I tried defining a test Moduleand Component, but Dagger won't generate the Component. I have also tried using the Componentthat I defined in my application and swapping the Modulewhen I build it, but the application's Componentdoesn't have injectmethods for my test classes. How can I provide a mock implementation of Randomfor testing?

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

Here's some sample code:

这是一些示例代码:

Application:

应用:

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:

模块:

@Module
public class PipeGameModule {

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

Base class for tests:

测试的基类:

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

or:

或者:

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

Test Module:

测试模块:

@Module
public class PipeGameTestModule {

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

采纳答案by tomrozb

This is currently impossible with Dagger 2 (as of v2.0.0) without some workarounds. You can read about it here.

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

More about possible workarounds:

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

回答by IgorGanapolsky

You have hit the nail on the head by saying:

你说:

application's Component doesn't have inject methods for my test classes

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

So, to get around this problem we can make a test version of your Application class. Then we can have a test version of your module. And to make it all run in a test, we can use Robolectric.

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

1) Create the test version of your Application class

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) Your original Application class needs to have initComponent()and pipeGameModule()methods

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) Your PipeGameTestModule should extend the production module with a constructor:

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

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

4) Now, in your junit test's setup()method, set this test module on your test app:

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

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

Now you can customize your test module how you originally wanted.

现在您可以按照您最初的需要自定义您的测试模块。

回答by Praveer Gupta

In my opinion you can approach this problem by looking at it from a different angle. You will easily be able to unit test your class by not depending upon Dagger for construction class under test with its mocked dependencies injected into it.

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

What I mean to say is that in the test setup you can:

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

  • Mock the dependencies of the class under test
  • Construct the class under test manually using the mocked dependencies
  • 模拟被测类的依赖关系
  • 使用模拟的依赖项手动构建被测类

We don't need to test whether dependencies are getting injected correctly as Dagger verifies the correctness of the dependency graph during compilation. So any such errors will be reported by failure of compilation. And that is why manual creation of class under test in the setup method should be acceptable.

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

Code example where dependency is injected using constructor in the class under test:

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

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

  ...
}

Code example where dependency is injected using field in the class under test (in case BoardModelis constructed by a framework):

使用被测类中的字段注入依赖项的代码示例(如果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() {}

  ...
}

回答by TjerkW

If you are using dagger2 with Android, you can use app flavours for providing mocking resources.

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

See here for a demo of flavours in mock testing(without dagger): https://www.youtube.com/watch?v=vdasFFfXKOY

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

This codebase has an example: https://github.com/googlecodelabs/android-testing

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

In your /src/prod/com/yourcompany/Component.javayou provide your production components.

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

In your /src/mock/com/yourcompany/Component.javayou provide your mocking components.

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

This allows you create builds of your app with or without mocking. It also allows parallel development (backend by one team, frontend app by another team), you can mock until api methods are avilable.

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

How my gradle commands look (its a Makefile):

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

install_mock:
    ./gradlew installMockDebug

install:
    ./gradlew installProdDebug

test_unit:
    ./gradlew testMockDebugUnitTest

test_integration_mock:
    ./gradlew connectedMockDebugAndroidTest

test_integration_prod:
    ./gradlew connectedProdDebugAndroidTest

回答by Roy Ben Shabat

I actually had the same issue and found a very simple solution. This is not the best possible solution I think but it will solve your problem.

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

Create a similar class in your app module:

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

public class ActivityTest<T extends ViewModelBase> {

    @Inject
    public T vm;
}

Then, in your AppComponent add:

然后,在您的 AppComponent 添加:

void inject(ActivityTest<LoginFragmentVM> activityTest);

Then you will be able to inject that in your test class.

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

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