【问题标题】:Using mockito to mock AccountManager使用 mockito 模拟 AccountManager
【发布时间】:2014-11-14 18:57:01
【问题描述】:

我正在使用 mockito 在 Activity 测试中模拟 AccountManager。

所以,我的测试代码如下:

public class PressuresListActivityUnitTest extends
    ActivityUnitTestCase<PressuresListActivity> {

// Test data.
private static final String ACCOUNT_TYPE = "com.example.android";
private static final Account ACCOUNT_1 = new Account("account1@gmail.com", ACCOUNT_TYPE);
private static final Account ACCOUNT_2 = new Account("account2@gmail.com", ACCOUNT_TYPE);
private static final Account[] TWO_ACCOUNTS = { ACCOUNT_1, ACCOUNT_2 };

@Mock
private AccountManager mMockAccountManager;

public PressuresListActivityUnitTest() {
    super(PressuresListActivity.class);
}

@Override
protected void setUp() throws Exception {
    super.setUp();

    setupDexmaker();
    // Initialize mockito.
    MockitoAnnotations.initMocks(this);
}

public void testAccountNotFound() {
    Mockito.when(mMockAccountManager.getAccounts())
            .thenReturn(TWO_ACCOUNTS);

    Intent intent = new Intent(Intent.ACTION_MAIN);
    startActivity(intent, null, null);
}

/**
 * Workaround for Mockito and JB-MR2 incompatibility to avoid
 * java.lang.IllegalArgumentException: dexcache == null
 *
 * @see <a href="https://code.google.com/p/dexmaker/issues/detail?id=2">
 *     https://code.google.com/p/dexmaker/issues/detail?id=2</a>
 */
private void setupDexmaker() {
    // Explicitly set the Dexmaker cache, so tests that use mockito work
    final String dexCache = getInstrumentation().getTargetContext().getCacheDir().getPath();
    System.setProperty("dexmaker.dexcache", dexCache);
}

以及将要测试的activity的onCreate方法:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_pressures_list);

    AccountManager am = AccountManager.get(this);
    Account[] accounts = am.getAccounts();
    if (accounts.length > 0) {
        Log.i("TAG", "it works!");
    }
}

但是当我运行测试时,AccountManager.getAccounts 不会返回测试中指定的帐户。

有什么想法吗?

【问题讨论】:

  • 你确定你Activity中的AccountManager是Mockito注入的实例吗?也许这就是为什么您的“何时”声明不起作用的原因。调试它。
  • @mate 我相信不是。但为什么呢?
  • @mate 我是使用 Mockito 的新手。我应该在被测 Activity 中手动注入 mockito 实例吗?
  • 只是调试。由 Mockito 初始化的实例在类名中有类似“mockito”的想法。您将在调试窗口中看到它。

标签: android mockito


【解决方案1】:

经过一番研究,我终于解决了这个问题。

Android 提供了一些在测试中使用的类,例如 MockContext、IsolatedContext。

http://developer.android.com/reference/android/test/mock/MockContext.html

http://developer.android.com/reference/android/test/IsolatedContext.html

为了完成这项工作,我创建了一个 ContextWrapper 的子类并覆盖 (??) getSystemService 方法。

根据文档:

“Context 的代理实现,只是将其所有调用委托给另一个 Context。可以子类化以修改行为而不更改原始 Context。”

http://developer.android.com/reference/android/content/ContextWrapper.html

这样,我使用常规的 AndroidActivityUnitTestCase 在 Activity 内部注入了原始上下文,但根据我的需要进行了修改。

看看这个:

public class FakeContextWrapper extends ContextWrapper {

    private static final String ACCOUNT_TYPE = "com.example.android";

    private static final Account ACCOUNT_1 = new Account("account1@gmail.com", ACCOUNT_TYPE);
    private static final Account ACCOUNT_2 = new Account("account2@gmail.com", ACCOUNT_TYPE);

    private static final Account[] TWO_ACCOUNTS = { ACCOUNT_1, ACCOUNT_2 };

    @Mock
    private AccountManager mMockAccountManager;

    public FakeContextWrapper(Context base) {
        super(base);

        MockitoAnnotations.initMocks(this);
        Mockito.when(mMockAccountManager.getAccounts()).thenReturn(TWO_ACCOUNTS);
    }

   @Override
   public Object getSystemService(String name) {
       if (Context.ACCOUNT_SERVICE.equals(name)) {
           return mMockAccountManager;
       } else {
           return super.getSystemService(name);
       }
   }
}

测试内部:

public void testAccountNotFound() {
    Context context = new FakeContextWrapper(getInstrumentation().getTargetContext());
    setActivityContext(context);
    Intent intent = new Intent(Intent.ACTION_MAIN);
    startActivity(intent, null, null);
    // TODO assertions.
}

最后是被测Activity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_pressures_list);

    AccountManager am = AccountManager.get(this);
    Account[] accounts = am.getAccounts();
    if (accounts.length == 0) {
        // TODO call login.
    } else {
        Log.i("TAG", "it works!");
    }
}

【讨论】:

【解决方案2】:

mockito 不是这样工作的。

import static org.mockito.Mockito.*;
...

public void testAccountNotFound() {

    AccountManager am = mock(AccountManager.class);
    when(am.getAccounts()).thenReturn(TWO_ACCOUNTS);

    // this is how you unit test something
    assertTrue(am.getAccounts().size == 2);
}

public void testMoreRealWorldExample() {

    AccountManager am = mock(AccountManager.class);
    when(am.getAccounts()).thenReturn(TWO_ACCOUNTS);

    /* try and create an account; createNewAccount() will call
       getAccounts() to find out how many accounts there already
       are in the system, and due to the above injection, it would
       think there are already two. Thus we can test to make sure
       users cannot create three or more accounts.
     */
    boolean accountCreated = am.createNewAccount();

    // maximum two accounts are allowed, so this should return false.
    assertFalse(accountCreated);
}

您不能直接使用 mockito 在对象中任意注入值,然后运行 ​​Activity。 Mockito 用于对您的对象进行单元测试,理想情况下对 Android 特定对象的引用最少,尽管有些引用是不可避免的。

请阅读the cookbook more closely,因为它非常详尽。

如果你想模拟和整个Activity,你需要查看in to Robolectric

【讨论】:

  • 其实我是在测试Activity,而不是AccountManager。更具体地说,我需要根据两个场景来测试 Activity 的行为。首先,当我开始活动并且没有帐户时,我需要调用LoginActivity。其次,当我开始活动并且帐户已经存在时,不需要调用。这就是为什么我需要模拟一些帐户。所以,恕我直言,您上面描述的测试不适合。如果有另一种方法来测试这种情况,请告诉我。谢谢!
  • 正如我所说,你不能像单独使用 mockito 那样测试 Activity。您需要考虑使用 Robolectric 或 Robotium。这些类型的测试远离“单元”测试,被认为是“功能”测试。它们更难以编程方式进行测试。
  • 我用过几次浓缩咖啡。我相信我现在很困惑。哈哈.. 谢谢!!
猜你喜欢
  • 2018-04-03
  • 1970-01-01
  • 2013-12-30
  • 2012-02-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-13
相关资源
最近更新 更多