Add beginning of a ViewModel test

mostly to demonstrate how those could look like
This commit is contained in:
Torsten Grote
2020-12-18 14:42:33 -03:00
parent 015ecb1d99
commit 205b4f77b2
12 changed files with 317 additions and 16 deletions

View File

@@ -0,0 +1,36 @@
package org.briarproject.briar.android;
import org.briarproject.bramble.api.system.AndroidExecutor;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
public class AndroidExecutorTestImpl implements AndroidExecutor {
private final Executor executor;
public AndroidExecutorTestImpl(Executor executor) {
this.executor = executor;
}
@Override
public <V> Future<V> runOnBackgroundThread(Callable<V> c) {
throw new IllegalStateException("not implemented");
}
@Override
public void runOnBackgroundThread(Runnable r) {
executor.execute(r);
}
@Override
public <V> Future<V> runOnUiThread(Callable<V> c) {
throw new IllegalStateException("not implemented");
}
@Override
public void runOnUiThread(Runnable r) {
executor.execute(r);
}
}

View File

@@ -0,0 +1,212 @@
package org.briarproject.briar.android.privategroup.list;
import android.app.Application;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.briar.android.AndroidExecutorTestImpl;
import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationItem;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Rule;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
import static edu.emory.mathcs.backport.java.util.Collections.emptyList;
import static edu.emory.mathcs.backport.java.util.Collections.singletonList;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.briar.android.viewmodel.LiveDataTestUtil.getOrAwaitValue;
import static org.briarproject.briar.api.client.MessageTracker.GroupCount;
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID;
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.MAJOR_VERSION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class GroupListViewModelTest extends BrambleMockTestCase {
@Rule
public final InstantTaskExecutorRule testRule =
new InstantTaskExecutorRule();
private final LifecycleManager lifecycleManager =
context.mock(LifecycleManager.class);
private final TransactionManager db =
context.mock(TransactionManager.class);
private final PrivateGroupManager groupManager =
context.mock(PrivateGroupManager.class);
private final GroupInvitationManager groupInvitationManager =
context.mock(GroupInvitationManager.class);
private final ContactManager contactManager =
context.mock(ContactManager.class);
private final AndroidNotificationManager notificationManager =
context.mock(AndroidNotificationManager.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final GroupListViewModel viewModel;
private final Group g1 = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Group g2 = getGroup(CLIENT_ID, MAJOR_VERSION);
private final PrivateGroup privateGroup1 =
new PrivateGroup(g1, "foo", getAuthor(), getRandomBytes(2));
private final PrivateGroup privateGroup2 =
new PrivateGroup(g2, "bar", getAuthor(), getRandomBytes(2));
private final AuthorInfo authorInfo1 =
new AuthorInfo(AuthorInfo.Status.UNVERIFIED);
private final AuthorInfo authorInfo2 =
new AuthorInfo(AuthorInfo.Status.VERIFIED);
private final GroupCount groupCount1 = new GroupCount(2, 1, 23L);
private final GroupCount groupCount2 = new GroupCount(5, 3, 42L);
private final GroupItem item1 =
new GroupItem(privateGroup1, authorInfo1, groupCount1, false);
private final GroupItem item2 =
new GroupItem(privateGroup2, authorInfo2, groupCount2, false);
public GroupListViewModelTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
Application app = context.mock(Application.class);
context.checking(new Expectations() {{
oneOf(eventBus).addListener(with(any(EventListener.class)));
}});
Executor dbExecutor = new ImmediateExecutor();
AndroidExecutor androidExecutor =
new AndroidExecutorTestImpl(dbExecutor);
viewModel = new GroupListViewModel(app, dbExecutor, lifecycleManager,
db, androidExecutor, groupManager, groupInvitationManager,
contactManager, notificationManager, eventBus);
}
@Test
public void testLoadGroupsException() throws Exception {
DbException dbException = new DbException();
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(lifecycleManager).waitForDatabase();
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(groupManager).getPrivateGroups(txn);
will(throwException(dbException));
}});
viewModel.loadGroups();
LiveResult<List<GroupItem>> result =
getOrAwaitValue(viewModel.getGroupItems());
assertTrue(result.hasError());
assertEquals(dbException, result.getException());
assertNull(result.getResultOrNull());
}
@Test
public void testLoadGroups() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(lifecycleManager).waitForDatabase();
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(groupManager).getPrivateGroups(txn);
will(returnValue(Arrays.asList(privateGroup1, privateGroup2)));
}});
expectLoadGroup(txn, privateGroup1, authorInfo1, groupCount1, false);
expectLoadGroup(txn, privateGroup2, authorInfo2, groupCount2, false);
viewModel.loadGroups();
// unpack updated live data
LiveResult<List<GroupItem>> result =
getOrAwaitValue(viewModel.getGroupItems());
assertFalse(result.hasError());
List<GroupItem> liveList = result.getResultOrNull();
assertNotNull(liveList);
// list is sorted by last message timestamp
assertEquals(Arrays.asList(item2, item1), liveList);
// group 1 gets dissolved by creator
Event dissolvedEvent = new GroupDissolvedEvent(privateGroup1.getId());
viewModel.eventOccurred(dissolvedEvent);
result = getOrAwaitValue(viewModel.getGroupItems());
liveList = result.getResultOrNull();
assertNotNull(liveList);
assertEquals(2, liveList.size());
// assert that list update includes dissolved group item
for (GroupItem item : liveList) {
if (item.getId().equals(privateGroup1.getId())) {
assertTrue(item.isDissolved());
} else if (item.getId().equals(privateGroup2.getId())) {
assertFalse(item.isDissolved());
} else fail();
}
}
@Test
public void testLoadNumInvitations() throws Exception {
context.checking(new Expectations() {{
oneOf(lifecycleManager).waitForDatabase();
oneOf(groupInvitationManager).getInvitations();
will(returnValue(emptyList()));
}});
viewModel.loadNumInvitations();
int num = getOrAwaitValue(viewModel.getNumInvitations());
assertEquals(0, num);
PrivateGroup pg = context.mock(PrivateGroup.class);
Contact c = getContact();
GroupInvitationItem item = new GroupInvitationItem(pg, c);
context.checking(new Expectations() {{
oneOf(lifecycleManager).waitForDatabase();
oneOf(groupInvitationManager).getInvitations();
will(returnValue(singletonList(item)));
}});
viewModel.loadNumInvitations();
num = getOrAwaitValue(viewModel.getNumInvitations());
assertEquals(1, num);
}
private void expectLoadGroup(Transaction txn, PrivateGroup privateGroup,
AuthorInfo authorInfo, GroupCount groupCount, boolean dissolved)
throws DbException {
context.checking(new DbExpectations() {{
oneOf(contactManager)
.getAuthorInfo(txn, privateGroup.getCreator().getId());
will(returnValue(authorInfo));
oneOf(groupManager).getGroupCount(txn, privateGroup.getId());
will(returnValue(groupCount));
oneOf(groupManager).isDissolved(txn, privateGroup.getId());
will(returnValue(dissolved));
}});
}
}

View File

@@ -0,0 +1,36 @@
/* Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0
https://gist.github.com/JoseAlcerreca/1e9ee05dcdd6a6a6fa1cbfc125559bba
*/
package org.briarproject.briar.android.viewmodel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
public class LiveDataTestUtil {
public static <T> T getOrAwaitValue(final LiveData<T> liveData)
throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
@Override
public void onChanged(@Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(2, TimeUnit.SECONDS)) {
throw new RuntimeException("LiveData value was never set.");
}
//noinspection unchecked
return (T) data[0];
}
}