Использование mockito для насмешки AccountManager

Я использую mockito для насмешки AccountManager в тесте Activity.

Итак, мой тестовый код выглядит следующим образом:

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

И метод создания, который будет проверен:

@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 НЕ возвращает учетные записи, указанные в тесте.

Любая идея?

2 ответа

Решение

После некоторых исследований я наконец решил проблему.

Android предоставляет некоторые классы для использования внутри тестов, такие как MockContext, IsolatedContext.

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

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

Чтобы сделать это, я создал подкласс ContextWrapper и переопределил (??) метод getSystemService.

Согласно документации:

"Прокси-реализация контекста, которая просто делегирует все свои вызовы другому контексту. Может быть разделена на подклассы для изменения поведения без изменения исходного контекста".

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

Таким образом, я внедрил исходный контекст, но изменил его в соответствии со своими потребностями, внутри Activity с помощью обычного AndroidActivityUnitTestCase.

Проверь это:

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

Наконец, тестируемая активность:

@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!");
    }
}

Это не так, как работает 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 объекты, хотя некоторые ссылки будут неизбежны.

Пожалуйста, прочитайте кулинарную книгу более внимательно, так как она довольно тщательная.

Если вы хотите издеваться и весь Activity, вам нужно заглянуть в Robolectric

Другие вопросы по тегам