在Android代码中模拟AsyncTasks和Intents创建的优雅方法

时间:2013-06-30 10:34:54

标签: java android unit-testing mocking mockito

我有一个Android活动,其中有像

这样的调用
final ConnectToServerAsyncTask task = new ConnectToServerAsyncTask(...);

final Intent intent = new Intent(this, SomeActivity.class);

为了对这个课进行单元测试,我需要能够模拟ConnectToServerAsyncTaskIntent的创建(例如使用Mockito)。

是否有比下面描述的方法更优雅的方法?

public class MainActivityOfTheApp extends Activity {
    private IAsyncTaskFactory asyncTaskFactory = new AsyncTaskFactory();
    private IIntentFactory intentFactory = new IntentFactory();

    public void setAsyncTaskFactory(final IAsyncTaskFactory aFactory)
    {
        asyncTaskFactory = aFactory;
    }

    public void setIntentFactory(final IIntentFactory aFactory)
    {
        intentFactory = aFactory;
    }

    @Override
    protected void onResume() {
        ...
        final ConnectToServerAsyncTask task = asyncTaskFactory.create(...);
        ...

        final Intent intent = intentFactory.create(this, OtherActivity.class);
        ...
    }
}

在单元测试中,我将创建一个MainActivityOfTheApp实例,然后使用setAsyncTaskFactorysetIntentFactory注入模拟。

1 个答案:

答案 0 :(得分:5)

PowerMock

您可以使用PowerMock模拟构造函数。

所以在你的情况下,测试看起来像这样:

@RunWith(PowerMockRunner.class)
@PrepareForTest(MainActivityOfTheApp.class)
public class MainActivityOfTheAppTest{
  private AsyncTaskFactory asyncTaskFactory;
  private IntentFactory intentFactory;
  private MainActivityOfTheApp mainActivityOfTheApp;

  @Before
  public void prepare() {
    asyncTaskFactory = PowerMockito.mock(AsyncTaskFactory.class);
    intentFactory = PowerMockito.mock(IntentFactory.class);
    PowerMockito.whenNew(AsyncTaskFactory.class).withNoArguments().thenReturn(asyncTaskFactory);
    PowerMockito.whenNew(IntentFactory.class).withNoArguments().thenReturn(intentFactory);
    mainActivityOfTheApp = new MainActivityOfTheApp();
  }

  @Test
  public void doTest() {
    //mainActivityOfTheApp has the mocks in final field inside, no need for the setters.
  }
}

我必须注意,PowerMock功能强大,但经常遇到复杂的类加载(例如OSGI环境)或代码覆盖工具(例如jacoco)的问题。它通常有效,但我浪费了一些时间。

没有PowerMock

第二种可能性是创建package private(无修饰符。为什么不受保护或公开?请参阅What's wrong with overridable method calls in constructors?)方法,它们会像这样调用构造函数:

public class MainActivityOfTheApp extends Activity {
    private final IAsyncTaskFactory asyncTaskFactory = constructAsyncTaskFactory();
    private final IIntentFactory intentFactory = constructIntentFactory();

    IAsyncTaskFactory constructAsyncTaskFactory()
    {
        return new AsyncTaskFactory();
    }

    IIntentFactory constructIntentFactory()
    {
        return new IntentFactory();
    }
    ...
}

然后在你的单元测试中(测试必须和测试类在同一个包中!)你覆盖了构造*()方法:

public class MainActivityOfTheAppTest{
  private AsyncTaskFactory asyncTaskFactory;
  private IntentFactory intentFactory;
  private MainActivityOfTheApp mainActivityOfTheApp;

  @Before
  public void prepare() {
    asyncTaskFactory = mock(AsyncTaskFactory.class);
    intentFactory = mock(IntentFactory.class);
    mainActivityOfTheApp = new HackedMainActivityOfTheApp();
  }

  @Test
  public void doTest() {
    //mainActivityOfTheApp has the mocks in final field inside, no need for the setters.
  }

  private class HackedMainActivityOfTheApp extends MainActivityOfTheApp {
        IAsyncTaskFactory constructAsyncTaskFactory()
        {
            return asyncTaskFactory;
        }

        IIntentFactory constructIntentFactory()
        {
            return intentFactory;
        }
  }
}

如果您对包含MainActivityOfTheApp的jar进行签名,则可以保护construct *方法。见https://stackoverflow.com/a/968612/337621

  

此外,补丁更难(你必须重新签署jar),类补丁是不可能的(单个包中的所有类必须具有相同的签名源)并且拆分jar变成一件苦差事。