【问题标题】:PowerMock + Robolectric + Dagger2. Part IPowerMock + Robolectric + Dagger2。第一部分
【发布时间】:2016-04-14 00:28:35
【问题描述】:


这个问题是从PowerMock + Robolectric + Dagger2的第一部分创建的
所以我又来了一点。对不起。

我测试自定义视图类,其中包含:

  1. android 用户界面元素
  2. 一些逻辑
  3. 静态方法调用
  4. dagger2 依赖项


所以我使用下一个工具进行测试

  1. 用于 UI 元素模拟的 Robolectric
  2. 用于逻辑测试的单元测试
  3. PowerMock 用于静态方法模拟

Robolectric + PowerMock 集成问题已知且解决方案已知 - https://github.com/robolectric/robolectric/wiki/Using-PowerMock
但是使用此解决方案 dagger2 依赖项失败。

注意代码
我的自定义视图 - ProgressTextView

public class ProgressTextView extends TextView {

    private String defaultText;
    private int fileSize;
    private String fileSizeString;
    private FileDownloaderI fileDownloader;

    @Inject
    FileDownloaderManager fileDownloaderManager;

    Subscription downloadProgressChannelSubscription;
    Subscription downloadCancelChannelSubscription;

    public ProgressTextView(Context context) {
        super(context);
        provideDependency();
    }

    public ProgressTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        provideDependency();
    }

    public ProgressTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        provideDependency();
    }

    private void provideDependency() {
        ApplicationSIP.get().applicationComponent().inject(this);
    }

}

ApplicationSIP

public class ApplicationSIP extends Application {

    public static volatile Context applicationContext;

    @NonNull
    private AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        applicationContext = getApplicationContext();
        Logger.registerLogger(this);

        appComponent = prepareApplicationComponent().build();
        appComponent.inject(this);
    }

    @NonNull
    protected DaggerAppComponent.Builder prepareApplicationComponent() {
        return DaggerAppComponent.builder()
                .appModule(new AppModule(getApplicationContext()))
                .tGModule(new TGModule());
    }

    @NonNull
    public AppComponent applicationComponent() {
        return appComponent;
    }

    @NonNull
    public static ApplicationSIP get() {
        return (ApplicationSIP) applicationContext.getApplicationContext();
    }

}

应用组件

@Component(modules = {AppModule.class, TGModule.class, MediaModule.class, StaticModule.class})
@Singleton
public interface AppComponent {   
    void inject(ApplicationSIP applicationSIP);
    void inject(ProgressDownloadView progressDownloadView);
    void inject(ProgressTextView progressTextView);
}

还有ProgressTextViewTest

@RunWith(RobolectricUnitTestRunner.class)
@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" })
@PrepareForTest(Formatter.class)
public class ProgressTextViewTest {

    @Rule
    public PowerMockRule rule = new PowerMockRule();
    Activity activity;

    @Before
    public void beforeTest() {
        // PowerMockito
        PowerMockito.mockStatic(Formatter.class);
        Mockito.when(Formatter.formatFileSize(anyObject(), anyInt())).thenReturn("");
        // Robolectic
        activity = Robolectric.setupActivity(Activity.class);
    }

    @Test
    public void init_FileDownloaded() {
        ProgressTextView progressTextView = new ProgressTextView(activity);
        ...
    }
}

所以当我开始这个测试时,我得到下一个异常:

java.lang.NullPointerException
at com.tg.osip.ApplicationSIP.get(ApplicationSIP.java:64)
at com.tg.osip.ui.general.views.ProgressTextView.provideDependency(ProgressTextView.java:60)
at com.tg.osip.ui.general.views.ProgressTextView.<init>(ProgressTextView.java:46)
at com.tg.osip.ui.general.views.ProgressTextViewTest.init_FileDownloaded(ProgressTextViewTest.java:67)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.powermock.modules.junit4.rule.PowerMockStatement$1.run(PowerMockRule.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.powermock.reflect.internal.WhiteboxImpl.performMethodInvocation(WhiteboxImpl.java:1873)
at org.powermock.reflect.internal.WhiteboxImpl.doInvokeMethod(WhiteboxImpl.java:773)
at org.powermock.reflect.internal.WhiteboxImpl.invokeMethod(WhiteboxImpl.java:638)
at org.powermock.reflect.Whitebox.invokeMethod(Whitebox.java:401)
at org.powermock.classloading.ClassloaderExecutor.execute(ClassloaderExecutor.java:98)
at org.powermock.classloading.ClassloaderExecutor.execute(ClassloaderExecutor.java:78)
at org.powermock.modules.junit4.rule.PowerMockStatement.evaluate(PowerMockRule.java:49)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

如果您更改 robolectric 活动初始放置:

@RunWith(RobolectricUnitTestRunner.class)
@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" })
@PrepareForTest(Formatter.class)
public class ProgressTextViewTest {

    @Rule
    public PowerMockRule rule = new PowerMockRule();
    Activity activity = Robolectric.setupActivity(Activity.class);

    @Before
    public void beforeTest() {
        // PowerMockito
        PowerMockito.mockStatic(Formatter.class);
        Mockito.when(Formatter.formatFileSize(anyObject(), anyInt())).thenReturn("");
    }

    @Test
    public void init_FileDownloaded() {
        ProgressTextView progressTextView = new ProgressTextView(activity);
        ...
    }
}

我得到其他异常:

java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:569)
at java.lang.StringBuffer.append(StringBuffer.java:369)
at java.io.StringWriter.write(StringWriter.java:94)
at com.thoughtworks.xstream.core.util.QuickWriter.flush(QuickWriter.java:73)
at com.thoughtworks.xstream.io.xml.PrettyPrintWriter.flush(PrettyPrintWriter.java:342)
at com.thoughtworks.xstream.XStream.toXML(XStream.java:858)
at com.thoughtworks.xstream.XStream.toXML(XStream.java:843)
at org.powermock.classloading.DeepCloner.clone(DeepCloner.java:53)
at org.powermock.classloading.ClassloaderExecutor.execute(ClassloaderExecutor.java:89)
at org.powermock.classloading.ClassloaderExecutor.execute(ClassloaderExecutor.java:78)
at org.powermock.modules.junit4.rule.PowerMockStatement.evaluate(PowerMockRule.java:49)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

【问题讨论】:

    标签: android unit-testing robolectric dagger-2 powermockito


    【解决方案1】:

    总结
    不要将 PowerMock 与 Robolectric 和 Dagger2 一起使用。不安全。
    现在我将包装类用于静态方法,并在 Dagger2 的帮助下注入这些类。所以我的测试类中没有静态方法,PowerMock 也不需要。
    我认为这是很正常的变体。
    并感谢 Eugen Martynov 的帮助。

    【讨论】:

      【解决方案2】:

      在回答您的问题之前。我会说:

      1. 您的视图做/知道的太多了。我说的是FileDownloaderManager的知识和如何获取依赖的知识
      2. 由于您使用的是注入,我会将静态函数包装到一个类中并注入它。编写/维护测试会容易得多
      3. 甚至AppComponent 类的名称都表示它不应该知道如何注入视图。考虑作用域注入
      4. 一些未成年人applicationContext。为什么是volatile,你期望它被非主线程访问吗?已经是ApplicationSIP了,为什么还要玩应用上下文和强制转换?

      终于有了答案。第一个堆栈跟踪表明 appContext 为空。所以你的应用程序的onCreate 没有被调用。我没有看到RobolectricUnitTestRunner 的代码,但我认为问题出在PowerMock 的使用上。第二种堆栈跟踪证明了这一点。所以我想说的答案是停止使用 PowerMock

      【讨论】:

      • 感谢 Eugen 的回答!)是的,也许我的观点做得太多了。但是这个视图在 RecyclerView 的 RecyclerAdapter 中的 ViewHolders 中。如果我点击这个视图,我想下载文件。所以视图必须显示下载的进度和结果。我不知道如何从 recyclerView 或 recyclerAdapter 获取视图并正确更新他而不更新完整的 ViewHolder。所以对于片段中的演示者和交互,我使用子组件。是的,很舒服
      • 现在我为静态方法使用包装类并注入它)
      • 在 RobolectricUnitTestRunner 中我设置了 ApplicationSIPTest。所以在 ApplicationSIPTest 我提供了测试模块
      • 关于 PowerMock。有了包装类,我们可以不用它)
      • 控制方向要相反。您可以使用 EvenBus 之类的东西来通知进度,或者保持对进度侦听器的弱引用(哪个视图将实现)。但主要思想 - 视图尽可能简单和孤立,适配器/演示者正在更新外部视图
      猜你喜欢
      • 1970-01-01
      • 2016-09-25
      • 2012-11-30
      • 2016-07-09
      • 2013-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多