diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java
index 8fd7479f457fc4f331e17d264da2817c782014ce..6faf538246dd264299c8de3364ac9cada2d9bc66 100644
--- a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java
@@ -1,147 +1,57 @@
 package org.briarproject;
 
-import net.jodah.concurrentunit.Waiter;
-
 import org.briarproject.api.blogs.Blog;
 import org.briarproject.api.blogs.BlogCommentHeader;
-import org.briarproject.api.blogs.BlogFactory;
 import org.briarproject.api.blogs.BlogManager;
 import org.briarproject.api.blogs.BlogPost;
-import org.briarproject.api.blogs.BlogPostFactory;
 import org.briarproject.api.blogs.BlogPostHeader;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.event.Event;
-import org.briarproject.api.event.EventListener;
-import org.briarproject.api.event.MessageStateChangedEvent;
-import org.briarproject.api.identity.AuthorFactory;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.identity.LocalAuthor;
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.sync.SyncSession;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.api.system.Clock;
-import org.briarproject.blogs.BlogsModule;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.transport.TransportModule;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
 import java.util.Collection;
 import java.util.Iterator;
-import java.util.concurrent.TimeoutException;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
 
 import static junit.framework.Assert.assertFalse;
-import static org.briarproject.TestPluginsModule.MAX_LATENCY;
 import static org.briarproject.api.blogs.MessageType.COMMENT;
 import static org.briarproject.api.blogs.MessageType.POST;
 import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
 import static org.briarproject.api.blogs.MessageType.WRAPPED_POST;
-import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
-import static org.briarproject.api.sync.ValidationManager.State.INVALID;
-import static org.briarproject.api.sync.ValidationManager.State.PENDING;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 public class BlogManagerTest extends BriarIntegrationTest {
 
-	private LifecycleManager lifecycleManager0, lifecycleManager1;
-	private SyncSessionFactory sync0, sync1;
 	private BlogManager blogManager0, blogManager1;
-	private ContactManager contactManager0, contactManager1;
-	private ContactId contactId0,contactId1;
-	private IdentityManager identityManager0, identityManager1;
-	private LocalAuthor author0, author1;
 	private Blog blog0, blog1;
 
-	@Inject
-	Clock clock;
-	@Inject
-	AuthorFactory authorFactory;
-	@Inject
-	CryptoComponent crypto;
-	@Inject
-	BlogFactory blogFactory;
-	@Inject
-	BlogPostFactory blogPostFactory;
-
-	// objects accessed from background threads need to be volatile
-	private volatile Waiter validationWaiter;
-	private volatile Waiter deliveryWaiter;
-
-	private final File testDir = TestUtils.getTestDirectory();
-	private final SecretKey master = TestUtils.getSecretKey();
-	private final int TIMEOUT = 15000;
-	private final String AUTHOR1 = "Author 1";
-	private final String AUTHOR2 = "Author 2";
-
-	private static final Logger LOG =
-			Logger.getLogger(BlogManagerTest.class.getName());
-
-	private BlogManagerTestComponent t0, t1;
-
 	@Rule
 	public ExpectedException thrown = ExpectedException.none();
 
 	@Before
 	public void setUp() throws Exception {
-		BlogManagerTestComponent component =
-				DaggerBlogManagerTestComponent.builder().build();
-		component.inject(this);
-		injectEagerSingletons(component);
-
-		assertTrue(testDir.mkdirs());
-		File t0Dir = new File(testDir, AUTHOR1);
-		t0 = DaggerBlogManagerTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
-		injectEagerSingletons(t0);
-		File t1Dir = new File(testDir, AUTHOR2);
-		t1 = DaggerBlogManagerTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
-		injectEagerSingletons(t1);
-
-		identityManager0 = t0.getIdentityManager();
-		identityManager1 = t1.getIdentityManager();
-		contactManager0 = t0.getContactManager();
-		contactManager1 = t1.getContactManager();
-		blogManager0 = t0.getBlogManager();
-		blogManager1 = t1.getBlogManager();
-		sync0 = t0.getSyncSessionFactory();
-		sync1 = t1.getSyncSessionFactory();
-
-		// initialize waiters fresh for each test
-		validationWaiter = new Waiter();
-		deliveryWaiter = new Waiter();
+		super.setUp();
+
+		author0 = identityManager0.getLocalAuthor();
+		author1 = identityManager1.getLocalAuthor();
+
+		blogManager0 = c0.getBlogManager();
+		blogManager1 = c1.getBlogManager();
+
+		blog0 = blogFactory.createBlog(author0);
+		blog1 = blogFactory.createBlog(author1);
 	}
 
 	@Test
 	public void testPersonalBlogInitialisation() throws Exception {
-		startLifecycles();
-
-		defaultInit();
-
 		Collection<Blog> blogs0 = blogManager0.getBlogs();
-		assertEquals(2, blogs0.size());
+		assertEquals(3, blogs0.size());
 		Iterator<Blog> i0 = blogs0.iterator();
 		assertEquals(author0, i0.next().getAuthor());
 		assertEquals(author1, i0.next().getAuthor());
+		assertEquals(author2, i0.next().getAuthor());
 
 		Collection<Blog> blogs1 = blogManager1.getBlogs();
 		assertEquals(2, blogs1.size());
@@ -163,15 +73,10 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		assertEquals(1, blogManager1.getBlogs(author0).size());
 		assertEquals(1, blogManager0.getBlogs(author1).size());
 		assertEquals(1, blogManager1.getBlogs(author1).size());
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testBlogPost() throws Exception {
-		startLifecycles();
-		defaultInit();
-
 		// check that blog0 has no posts
 		final String body = TestUtils.getRandomString(42);
 		Collection<BlogPostHeader> headers0 =
@@ -197,8 +102,7 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		assertEquals(0, headers1.size());
 
 		// sync the post over
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync0To1(1, true);
 
 		// make sure post arrived
 		headers1 = blogManager1.getPostHeaders(blog0.getId());
@@ -207,15 +111,10 @@ public class BlogManagerTest extends BriarIntegrationTest {
 
 		// check that body is there
 		assertEquals(body, blogManager1.getPostBody(p.getMessage().getId()));
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testBlogPostInWrongBlog() throws Exception {
-		startLifecycles();
-		defaultInit();
-
 		// add a post to blog1
 		final String body = TestUtils.getRandomString(42);
 		BlogPost p = blogPostFactory
@@ -229,22 +128,16 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		assertEquals(1, headers0.size());
 
 		// sync the post over
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// make sure post did not arrive, because of wrong signature
 		Collection<BlogPostHeader> headers1 =
 				blogManager1.getPostHeaders(blog1.getId());
 		assertEquals(0, headers1.size());
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testCanNotRemoveContactsPersonalBlog() throws Exception {
-		startLifecycles();
-		defaultInit();
-
 		assertFalse(blogManager0.canBeRemoved(blog1.getId()));
 		assertFalse(blogManager1.canBeRemoved(blog0.getId()));
 
@@ -257,15 +150,10 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		// blogs have not been removed
 		assertEquals(2, blogManager0.getBlogs().size());
 		assertEquals(2, blogManager1.getBlogs().size());
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testBlogComment() throws Exception {
-		startLifecycles();
-		defaultInit();
-
 		// add a post to blog0
 		final String body = TestUtils.getRandomString(42);
 		BlogPost p = blogPostFactory
@@ -274,8 +162,7 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		blogManager0.addLocalPost(p);
 
 		// sync the post over
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync0To1(1, true);
 
 		// make sure post arrived
 		Collection<BlogPostHeader> headers1 =
@@ -290,8 +177,7 @@ public class BlogManagerTest extends BriarIntegrationTest {
 						headers1.iterator().next());
 
 		// sync comment over
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 2);
+		sync1To0(2, true);
 
 		// make sure comment and wrapped post arrived
 		Collection<BlogPostHeader> headers0 =
@@ -307,15 +193,10 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		// 1 has only their own comment in their blog
 		headers1 = blogManager1.getPostHeaders(blog1.getId());
 		assertEquals(1, headers1.size());
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testBlogCommentOnOwnPost() throws Exception {
-		startLifecycles();
-		defaultInit();
-
 		// add a post to blog0
 		final String body = TestUtils.getRandomString(42);
 		BlogPost p = blogPostFactory
@@ -335,8 +216,7 @@ public class BlogManagerTest extends BriarIntegrationTest {
 				.addLocalComment(author0, blog0.getId(), comment, header);
 
 		// sync the post and comment over
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 2);
+		sync0To1(2, true);
 
 		// make sure post arrived
 		Collection<BlogPostHeader> headers1 =
@@ -349,15 +229,10 @@ public class BlogManagerTest extends BriarIntegrationTest {
 				assertEquals(comment, ((BlogCommentHeader)h).getComment());
 			}
 		}
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testCommentOnComment() throws Exception {
-		startLifecycles();
-		defaultInit();
-
 		// add a post to blog0
 		final String body = TestUtils.getRandomString(42);
 		BlogPost p = blogPostFactory
@@ -366,8 +241,7 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		blogManager0.addLocalPost(p);
 
 		// sync the post over
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync0To1(1, true);
 
 		// make sure post arrived
 		Collection<BlogPostHeader> headers1 =
@@ -381,8 +255,7 @@ public class BlogManagerTest extends BriarIntegrationTest {
 						headers1.iterator().next());
 
 		// sync comment over
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 2);
+		sync1To0(2, true);
 
 		// make sure comment and wrapped post arrived
 		Collection<BlogPostHeader> headers0 =
@@ -399,8 +272,7 @@ public class BlogManagerTest extends BriarIntegrationTest {
 				.addLocalComment(author0, blog0.getId(), comment, cHeader);
 
 		// sync comment over
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 3);
+		sync0To1(3, true);
 
 		// check that comment arrived
 		headers1 =
@@ -421,8 +293,7 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		blogManager1.addLocalComment(author1, blog1.getId(), comment2, cHeader);
 
 		// sync comment over
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 4);
+		sync1To0(4, true);
 
 		// make sure new comment arrived
 		headers0 =
@@ -448,16 +319,10 @@ public class BlogManagerTest extends BriarIntegrationTest {
 			}
 		}
 		assertTrue(satisfied);
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testCommentOnOwnComment() throws Exception {
-
-		startLifecycles();
-		defaultInit();
-
 		// add a post to blog0
 		final String body = TestUtils.getRandomString(42);
 		BlogPost p = blogPostFactory
@@ -466,8 +331,7 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		blogManager0.addLocalPost(p);
 
 		// sync the post over
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync0To1(1, true);
 
 		// make sure post arrived
 		Collection<BlogPostHeader> headers1 =
@@ -492,128 +356,12 @@ public class BlogManagerTest extends BriarIntegrationTest {
 		blogManager1.addLocalComment(author1, blog1.getId(), comment, ch);
 
 		// sync both comments over (2 comments + 1 wrapped post)
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 3);
+		sync1To0(3, true);
 
 		// make sure both comments arrived
 		Collection<BlogPostHeader> headers0 =
 				blogManager0.getPostHeaders(blog1.getId());
 		assertEquals(2, headers0.size());
-
-		stopLifecycles();
-	}
-
-	@After
-	public void tearDown() throws Exception {
-		TestUtils.deleteTestDirectory(testDir);
-	}
-
-	private class Listener implements EventListener {
-		@Override
-		public void eventOccurred(Event e) {
-			if (e instanceof MessageStateChangedEvent) {
-				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
-				if (!event.isLocal()) {
-					if (event.getState() == DELIVERED) {
-						deliveryWaiter.resume();
-					} else if (event.getState() == INVALID ||
-							event.getState() == PENDING) {
-						validationWaiter.resume();
-					}
-				}
-			}
-		}
-	}
-
-	private void defaultInit() throws DbException {
-		getDefaultIdentities();
-		addDefaultContacts();
-		listenToEvents();
-	}
-
-	private void getDefaultIdentities() throws DbException {
-		author0 = identityManager0.getLocalAuthor();
-		author1 = identityManager1.getLocalAuthor();
-		blog0 = blogFactory.createBlog(author0);
-		blog1 = blogFactory.createBlog(author1);
-	}
-
-	private void addDefaultContacts() throws DbException {
-		// sharer adds invitee as contact
-		contactId1 = contactManager0.addContact(author1,
-				author0.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		// invitee adds sharer back
-		contactId0 = contactManager1.addContact(author0,
-				author1.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-	}
-
-	private void listenToEvents() {
-		Listener listener0 = new Listener();
-		t0.getEventBus().addListener(listener0);
-		Listener listener1 = new Listener();
-		t1.getEventBus().addListener(listener1);
-	}
-
-	private void sync0To1() throws IOException, TimeoutException {
-		deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-	}
-
-	private void sync1To0() throws IOException, TimeoutException {
-		deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
-	}
-
-	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
-			SyncSessionFactory toSync, ContactId toId, String debug)
-			throws IOException, TimeoutException {
-
-		if (debug != null) LOG.info("TEST: Sending message from " + debug);
-
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		// Create an outgoing sync session
-		SyncSession sessionFrom =
-				fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
-		// Write whatever needs to be written
-		sessionFrom.run();
-		out.close();
-
-		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-		// Create an incoming sync session
-		SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
-		// Read whatever needs to be read
-		sessionTo.run();
-		in.close();
-	}
-
-	private void startLifecycles() throws InterruptedException {
-		// Start the lifecycle manager and wait for it to finish
-		lifecycleManager0 = t0.getLifecycleManager();
-		lifecycleManager1 = t1.getLifecycleManager();
-		lifecycleManager0.startServices(AUTHOR1);
-		lifecycleManager1.startServices(AUTHOR2);
-		lifecycleManager0.waitForStartup();
-		lifecycleManager1.waitForStartup();
-	}
-
-	private void stopLifecycles() throws InterruptedException {
-		// Clean up
-		lifecycleManager0.stopServices();
-		lifecycleManager1.stopServices();
-		lifecycleManager0.waitForShutdown();
-		lifecycleManager1.waitForShutdown();
-	}
-
-	private void injectEagerSingletons(BlogManagerTestComponent component) {
-		component.inject(new LifecycleModule.EagerSingletons());
-		component.inject(new BlogsModule.EagerSingletons());
-		component.inject(new CryptoModule.EagerSingletons());
-		component.inject(new ContactModule.EagerSingletons());
-		component.inject(new TransportModule.EagerSingletons());
-		component.inject(new SyncModule.EagerSingletons());
-		component.inject(new PropertiesModule.EagerSingletons());
 	}
 
 }
diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTestComponent.java
deleted file mode 100644
index 41d15eb9e660ce8e94cf9840aee2f1ce4a97700d..0000000000000000000000000000000000000000
--- a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTestComponent.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.briarproject;
-
-import org.briarproject.api.blogs.BlogManager;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.event.EventBus;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.blogs.BlogsModule;
-import org.briarproject.clients.ClientsModule;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.data.DataModule;
-import org.briarproject.db.DatabaseModule;
-import org.briarproject.event.EventModule;
-import org.briarproject.identity.IdentityModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sharing.SharingModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.system.SystemModule;
-import org.briarproject.transport.TransportModule;
-
-import javax.inject.Singleton;
-
-import dagger.Component;
-
-@Singleton
-@Component(modules = {
-		TestDatabaseModule.class,
-		TestPluginsModule.class,
-		TestSeedProviderModule.class,
-		ClientsModule.class,
-		ContactModule.class,
-		CryptoModule.class,
-		DataModule.class,
-		DatabaseModule.class,
-		EventModule.class,
-		BlogsModule.class,
-		IdentityModule.class,
-		LifecycleModule.class,
-		PropertiesModule.class,
-		SharingModule.class,
-		SyncModule.class,
-		SystemModule.class,
-		TransportModule.class
-})
-interface BlogManagerTestComponent {
-
-	void inject(BlogManagerTest testCase);
-
-	void inject(ContactModule.EagerSingletons init);
-
-	void inject(CryptoModule.EagerSingletons init);
-
-	void inject(BlogsModule.EagerSingletons init);
-
-	void inject(LifecycleModule.EagerSingletons init);
-
-	void inject(PropertiesModule.EagerSingletons init);
-
-	void inject(SyncModule.EagerSingletons init);
-
-	void inject(TransportModule.EagerSingletons init);
-
-	LifecycleManager getLifecycleManager();
-
-	EventBus getEventBus();
-
-	IdentityManager getIdentityManager();
-
-	ContactManager getContactManager();
-
-	BlogManager getBlogManager();
-
-	SyncSessionFactory getSyncSessionFactory();
-
-}
diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java
index 4f47c3444ad8bacf7f7dc57dfad82725ee8664f1..96308d91ffa4677c8aaf955150c7e0eccb637573 100644
--- a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java
@@ -6,206 +6,112 @@ import org.briarproject.api.blogs.Blog;
 import org.briarproject.api.blogs.BlogInvitationRequest;
 import org.briarproject.api.blogs.BlogInvitationResponse;
 import org.briarproject.api.blogs.BlogManager;
-import org.briarproject.api.blogs.BlogPostFactory;
 import org.briarproject.api.blogs.BlogSharingManager;
-import org.briarproject.api.clients.ContactGroupFactory;
-import org.briarproject.api.clients.MessageTracker;
 import org.briarproject.api.contact.Contact;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.BlogInvitationReceivedEvent;
 import org.briarproject.api.event.BlogInvitationResponseReceivedEvent;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventListener;
-import org.briarproject.api.event.MessageStateChangedEvent;
-import org.briarproject.api.identity.AuthorFactory;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.identity.LocalAuthor;
-import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sharing.InvitationMessage;
 import org.briarproject.api.sync.GroupId;
-import org.briarproject.api.sync.SyncSession;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.api.sync.ValidationManager.State;
-import org.briarproject.api.system.Clock;
-import org.briarproject.blogs.BlogsModule;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sharing.SharingModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.transport.TransportModule;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.TimeoutException;
-import java.util.logging.Logger;
 
-import javax.inject.Inject;
-
-import static org.briarproject.TestPluginsModule.MAX_LATENCY;
 import static org.briarproject.api.blogs.BlogSharingManager.CLIENT_ID;
-import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
-import static org.briarproject.api.sync.ValidationManager.State.INVALID;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 
-	private LifecycleManager lifecycleManager0, lifecycleManager1,
-			lifecycleManager2;
-	private SyncSessionFactory sync0, sync1;
-	private BlogManager blogManager0, blogManager1;
-	private MessageTracker messageTracker0, messageTracker1;
-	private ContactManager contactManager0, contactManager1, contactManager2;
-	private Contact contact1, contact2, contact01, contact02;
-	private ContactId contactId1, contactId01;
-	private IdentityManager identityManager0, identityManager1,
-			identityManager2;
-	private LocalAuthor author0, author1, author2;
+	private BlogManager blogManager1;
 	private Blog blog0, blog1, blog2;
 	private SharerListener listener0;
 	private InviteeListener listener1;
 
-	@Inject
-	Clock clock;
-	@Inject
-	AuthorFactory authorFactory;
-	@Inject
-	ContactGroupFactory contactGroupFactory;
-	@Inject
-	BlogPostFactory blogPostFactory;
-	@Inject
-	CryptoComponent cryptoComponent;
-
 	// objects accessed from background threads need to be volatile
 	private volatile BlogSharingManager blogSharingManager0;
 	private volatile BlogSharingManager blogSharingManager1;
 	private volatile BlogSharingManager blogSharingManager2;
 	private volatile Waiter eventWaiter;
-	private volatile Waiter msgWaiter;
-
-	private final File testDir = TestUtils.getTestDirectory();
-	private final SecretKey master = TestUtils.getSecretKey();
-	private final int TIMEOUT = 15000;
-	private final String SHARER = "Sharer";
-	private final String INVITEE = "Invitee";
-	private final String CONTACT2 = "Contact2";
-
-	private static final Logger LOG =
-			Logger.getLogger(BlogSharingIntegrationTest.class.getName());
-
-	private BlogSharingIntegrationTestComponent t0, t1, t2;
 
 	@Rule
 	public ExpectedException thrown = ExpectedException.none();
 
 	@Before
-	public void setUp() {
-		BlogSharingIntegrationTestComponent component =
-				DaggerBlogSharingIntegrationTestComponent.builder().build();
-		component.inject(this);
-		injectEagerSingletons(component);
-
-		assertTrue(testDir.mkdirs());
-		File t0Dir = new File(testDir, SHARER);
-		t0 = DaggerBlogSharingIntegrationTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
-		injectEagerSingletons(t0);
-		File t1Dir = new File(testDir, INVITEE);
-		t1 = DaggerBlogSharingIntegrationTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
-		injectEagerSingletons(t1);
-		File t2Dir = new File(testDir, CONTACT2);
-		t2 = DaggerBlogSharingIntegrationTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
-		injectEagerSingletons(t2);
-
-		identityManager0 = t0.getIdentityManager();
-		identityManager1 = t1.getIdentityManager();
-		identityManager2 = t2.getIdentityManager();
-		contactManager0 = t0.getContactManager();
-		contactManager1 = t1.getContactManager();
-		contactManager2 = t2.getContactManager();
-		blogManager0 = t0.getBlogManager();
-		blogManager1 = t1.getBlogManager();
-		messageTracker0 = t0.getMessageTracker();
-		messageTracker1 = t1.getMessageTracker();
-		blogSharingManager0 = t0.getBlogSharingManager();
-		blogSharingManager1 = t1.getBlogSharingManager();
-		blogSharingManager2 = t2.getBlogSharingManager();
-		sync0 = t0.getSyncSessionFactory();
-		sync1 = t1.getSyncSessionFactory();
+	public void setUp() throws Exception {
+		super.setUp();
+
+		BlogManager blogManager0 = c0.getBlogManager();
+		blogManager1 = c1.getBlogManager();
+		blogSharingManager0 = c0.getBlogSharingManager();
+		blogSharingManager1 = c1.getBlogSharingManager();
+		blogSharingManager2 = c2.getBlogSharingManager();
+
+		blog0 = blogManager0.getPersonalBlog(author0);
+		blog1 = blogManager0.getPersonalBlog(author1);
+		blog2 = blogManager0.getPersonalBlog(author2);
 
 		// initialize waiters fresh for each test
 		eventWaiter = new Waiter();
-		msgWaiter = new Waiter();
 	}
 
 	@Test
 	public void testPersonalBlogCannotBeSharedWithOwner() throws Exception {
-		startLifecycles();
-		defaultInit(true);
+		listenToEvents(true);
 
-		assertFalse(blogSharingManager0.canBeShared(blog1.getId(), contact1));
-		assertFalse(blogSharingManager0.canBeShared(blog2.getId(), contact2));
-		assertFalse(blogSharingManager1.canBeShared(blog0.getId(), contact01));
-		assertFalse(blogSharingManager2.canBeShared(blog0.getId(), contact02));
+		assertFalse(blogSharingManager0.canBeShared(blog1.getId(),
+				contact1From0));
+		assertFalse(blogSharingManager0.canBeShared(blog2.getId(),
+				contact2From0));
+		assertFalse(blogSharingManager1.canBeShared(blog0.getId(),
+				contact0From1));
+		assertFalse(blogSharingManager2.canBeShared(blog0.getId(),
+				contact0From2));
 
 		// create invitation
 		blogSharingManager0
-				.sendInvitation(blog1.getId(), contactId1, "Hi!");
+				.sendInvitation(blog1.getId(), contactId1From0, "Hi!");
 
 		// sync invitation
-		sync0To1();
+		sync0To1(1, false);
 		// make sure the invitee ignored the request for their own blog
 		assertFalse(listener1.requestReceived);
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testSuccessfulSharing() throws Exception {
-		startLifecycles();
-
 		// initialize and let invitee accept all requests
-		defaultInit(true);
+		listenToEvents(true);
 
 		// send invitation
 		blogSharingManager0
-				.sendInvitation(blog2.getId(), contactId1, "Hi!");
+				.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
 
 		// invitee has own blog and that of the sharer
 		assertEquals(2, blogManager1.getBlogs().size());
 
 		// get sharing group and assert group message count
-		GroupId g = contactGroupFactory.createContactGroup(CLIENT_ID, contact1)
+		GroupId g = contactGroupFactory.createContactGroup(CLIENT_ID,
+				contact1From0)
 				.getId();
 		assertGroupCount(messageTracker0, g, 1, 0);
 
 		// sync first request message
-		sync0To1();
+		sync0To1(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener1.requestReceived);
 		assertGroupCount(messageTracker1, g, 2, 1);
 
 		// sync response back
-		sync1To0();
+		sync1To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.responseReceived);
 		assertGroupCount(messageTracker0, g, 2, 1);
@@ -217,7 +123,7 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 		// invitee has one invitation message from sharer
 		List<InvitationMessage> list =
 				new ArrayList<>(blogSharingManager1
-						.getInvitationMessages(contactId01));
+						.getInvitationMessages(contactId0From1));
 		assertEquals(2, list.size());
 		// check other things are alright with the message
 		for (InvitationMessage m : list) {
@@ -227,49 +133,47 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 				assertFalse(invitation.isAvailable());
 				assertEquals(blog2.getAuthor().getName(),
 						invitation.getBlogAuthorName());
-				assertEquals(contactId1, invitation.getContactId());
+				assertEquals(contactId1From0, invitation.getContactId());
 				assertEquals("Hi!", invitation.getMessage());
 			} else {
 				BlogInvitationResponse response =
 						(BlogInvitationResponse) m;
-				assertEquals(contactId01, response.getContactId());
+				assertEquals(contactId0From1, response.getContactId());
 				assertTrue(response.wasAccepted());
 				assertTrue(response.isLocal());
 			}
 		}
 		// sharer has own invitation message and response
 		assertEquals(2,
-				blogSharingManager0.getInvitationMessages(contactId1)
+				blogSharingManager0.getInvitationMessages(contactId1From0)
 						.size());
 		// blog can not be shared again
-		assertFalse(blogSharingManager0.canBeShared(blog2.getId(), contact1));
-		assertFalse(blogSharingManager1.canBeShared(blog2.getId(), contact01));
+		assertFalse(blogSharingManager0.canBeShared(blog2.getId(),
+				contact1From0));
+		assertFalse(blogSharingManager1.canBeShared(blog2.getId(),
+				contact0From1));
 
 		// group message count is still correct
 		assertGroupCount(messageTracker0, g, 2, 1);
 		assertGroupCount(messageTracker1, g, 2, 1);
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testDeclinedSharing() throws Exception {
-		startLifecycles();
-
 		// initialize and let invitee deny all requests
-		defaultInit(false);
+		listenToEvents(false);
 
 		// send invitation
 		blogSharingManager0
-				.sendInvitation(blog2.getId(), contactId1, null);
+				.sendInvitation(blog2.getId(), contactId1From0, null);
 
 		// sync first request message
-		sync0To1();
+		sync0To1(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener1.requestReceived);
 
 		// sync response back
-		sync1To0();
+		sync1To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.responseReceived);
 
@@ -282,7 +186,7 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 		// invitee has one invitation message from sharer and one response
 		List<InvitationMessage> list =
 				new ArrayList<>(blogSharingManager1
-						.getInvitationMessages(contactId01));
+						.getInvitationMessages(contactId0From1));
 		assertEquals(2, list.size());
 		// check things are alright with the  message
 		for (InvitationMessage m : list) {
@@ -292,44 +196,40 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 				assertFalse(invitation.isAvailable());
 				assertEquals(blog2.getAuthor().getName(),
 						invitation.getBlogAuthorName());
-				assertEquals(contactId1, invitation.getContactId());
+				assertEquals(contactId1From0, invitation.getContactId());
 				assertEquals(null, invitation.getMessage());
 			} else {
 				BlogInvitationResponse response =
 						(BlogInvitationResponse) m;
-				assertEquals(contactId01, response.getContactId());
+				assertEquals(contactId0From1, response.getContactId());
 				assertFalse(response.wasAccepted());
 				assertTrue(response.isLocal());
 			}
 		}
 		// sharer has own invitation message and response
 		assertEquals(2,
-				blogSharingManager0.getInvitationMessages(contactId1)
+				blogSharingManager0.getInvitationMessages(contactId1From0)
 						.size());
 		// blog can be shared again
-		assertTrue(blogSharingManager0.canBeShared(blog2.getId(), contact1));
-
-		stopLifecycles();
+		assertTrue(blogSharingManager0.canBeShared(blog2.getId(), contact1From0));
 	}
 
 	@Test
 	public void testInviteeLeavesAfterFinished() throws Exception {
-		startLifecycles();
-
 		// initialize and let invitee accept all requests
-		defaultInit(true);
+		listenToEvents(true);
 
 		// send invitation
 		blogSharingManager0
-				.sendInvitation(blog2.getId(), contactId1, "Hi!");
+				.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
 
 		// sync first request message
-		sync0To1();
+		sync0To1(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener1.requestReceived);
 
 		// sync response back
-		sync1To0();
+		sync1To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.responseReceived);
 
@@ -340,16 +240,16 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 
 		// sharer shares blog with invitee
 		assertTrue(blogSharingManager0.getSharedWith(blog2.getId())
-				.contains(contact1));
+				.contains(contact1From0));
 		// invitee gets blog shared by sharer
 		assertTrue(blogSharingManager1.getSharedBy(blog2.getId())
-				.contains(contact01));
+				.contains(contact0From1));
 
 		// invitee un-subscribes from blog
 		blogManager1.removeBlog(blog2);
 
 		// send leave message to sharer
-		sync1To0();
+		sync1To0(1, true);
 
 		// blog is gone
 		assertEquals(0, blogSharingManager0.getInvitations().size());
@@ -357,41 +257,30 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 
 		// sharer no longer shares blog with invitee
 		assertFalse(blogSharingManager0.getSharedWith(blog2.getId())
-				.contains(contact1));
+				.contains(contact1From0));
 		// invitee no longer gets blog shared by sharer
 		assertFalse(blogSharingManager1.getSharedBy(blog2.getId())
-				.contains(contact01));
+				.contains(contact0From1));
 		// blog can be shared again
-		assertTrue(blogSharingManager0.canBeShared(blog2.getId(), contact1));
-		assertTrue(blogSharingManager1.canBeShared(blog2.getId(), contact01));
-
-		stopLifecycles();
+		assertTrue(blogSharingManager0.canBeShared(blog2.getId(), contact1From0));
+		assertTrue(blogSharingManager1.canBeShared(blog2.getId(), contact0From1));
 	}
 
 	@Test
 	public void testInvitationForExistingBlog() throws Exception {
-		startLifecycles();
-
 		// initialize and let invitee accept all requests
-		defaultInit(true);
+		listenToEvents(true);
 
 		// 1 and 2 are adding each other
-		contactManager1.addContact(author2,
-				author1.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		contactManager2.addContact(author1,
-				author2.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
+		addContacts1And2();
 		assertEquals(3, blogManager1.getBlogs().size());
 
 		// sharer sends invitation for 2's blog to 1
 		blogSharingManager0
-				.sendInvitation(blog2.getId(), contactId1, "Hi!");
+				.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
 
 		// sync first request message
-		sync0To1();
+		sync0To1(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener1.requestReceived);
 
@@ -399,44 +288,40 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 		Collection<Contact> contacts =
 				blogSharingManager1.getSharedBy(blog2.getId());
 		assertEquals(1, contacts.size());
-		assertTrue(contacts.contains(contact01));
+		assertTrue(contacts.contains(contact0From1));
 
 		// make sure 1 knows that they have blog2 already
 		Collection<InvitationMessage> messages =
-				blogSharingManager1.getInvitationMessages(contactId01);
+				blogSharingManager1.getInvitationMessages(contactId0From1);
 		assertEquals(2, messages.size());
 		assertEquals(blog2, blogManager1.getBlog(blog2.getId()));
 
 		// sync response back
-		sync1To0();
+		sync1To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.responseReceived);
 
 		// blog was not added, because it was there already
 		assertEquals(0, blogSharingManager0.getInvitations().size());
 		assertEquals(3, blogManager1.getBlogs().size());
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testRemovingSharedBlog() throws Exception {
-		startLifecycles();
-
 		// initialize and let invitee accept all requests
-		defaultInit(true);
+		listenToEvents(true);
 
 		// send invitation
 		blogSharingManager0
-				.sendInvitation(blog2.getId(), contactId1, "Hi!");
+				.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
 
 		// sync first request message
-		sync0To1();
+		sync0To1(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener1.requestReceived);
 
 		// sync response back
-		sync1To0();
+		sync1To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.responseReceived);
 
@@ -445,11 +330,11 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 		Collection<Contact> sharedWith =
 				blogSharingManager0.getSharedWith(blog2.getId());
 		assertEquals(1, sharedWith.size());
-		assertEquals(contact1, sharedWith.iterator().next());
+		assertEquals(contact1From0, sharedWith.iterator().next());
 		Collection<Contact> sharedBy =
 				blogSharingManager1.getSharedBy(blog2.getId());
 		assertEquals(1, sharedBy.size());
-		assertEquals(contact01, sharedBy.iterator().next());
+		assertEquals(contact0From1, sharedBy.iterator().next());
 
 		// shared blog can be removed
 		assertTrue(blogManager1.canBeRemoved(blog2.getId()));
@@ -458,32 +343,28 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 		blogManager1.removeBlog(blog2);
 
 		// sync LEAVE message
-		sync1To0();
+		sync1To0(1, true);
 
 		// sharer does not share this blog anymore with invitee
 		sharedWith =
 				blogSharingManager0.getSharedWith(blog2.getId());
 		assertEquals(0, sharedWith.size());
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testSharedBlogBecomesPermanent() throws Exception {
-		startLifecycles();
-
-		// initialize and let invitee accept all requests
-		defaultInit(true);
+		// let invitee accept all requests
+		listenToEvents(true);
 
 		// invitee only sees two blogs
 		assertEquals(2, blogManager1.getBlogs().size());
 
 		// sharer sends invitation for 2's blog to 1
 		blogSharingManager0
-				.sendInvitation(blog2.getId(), contactId1, "Hi!");
+				.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
 
 		// sync first request message
-		sync0To1();
+		sync0To1(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener1.requestReceived);
 
@@ -491,10 +372,10 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 		Collection<Contact> contacts =
 				blogSharingManager1.getSharedBy(blog2.getId());
 		assertEquals(1, contacts.size());
-		assertTrue(contacts.contains(contact01));
+		assertTrue(contacts.contains(contact0From1));
 
 		// sync response back
-		sync1To0();
+		sync1To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.responseReceived);
 
@@ -503,26 +384,11 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 		assertTrue(blogManager1.canBeRemoved(blog2.getId()));
 
 		// 1 and 2 are adding each other
-		contactManager1.addContact(author2,
-				author1.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		contactManager2.addContact(author1,
-				author2.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
+		addContacts1And2();
 		assertEquals(3, blogManager1.getBlogs().size());
 
 		// now blog can not be removed anymore
 		assertFalse(blogManager1.canBeRemoved(blog2.getId()));
-
-		stopLifecycles();
-	}
-
-
-	@After
-	public void tearDown() throws InterruptedException {
-		TestUtils.deleteTestDirectory(testDir);
 	}
 
 	private class SharerListener implements EventListener {
@@ -531,17 +397,10 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 
 		@Override
 		public void eventOccurred(Event e) {
-			if (e instanceof MessageStateChangedEvent) {
-				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
-				State s = event.getState();
-				if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
-					LOG.info("TEST: Sharer received message");
-					msgWaiter.resume();
-				}
-			} else if (e instanceof BlogInvitationResponseReceivedEvent) {
+			if (e instanceof BlogInvitationResponseReceivedEvent) {
 				BlogInvitationResponseReceivedEvent event =
 						(BlogInvitationResponseReceivedEvent) e;
-				eventWaiter.assertEquals(contactId1, event.getContactId());
+				eventWaiter.assertEquals(contactId1From0, event.getContactId());
 				responseReceived = true;
 				eventWaiter.resume();
 			}
@@ -549,10 +408,10 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 			else if (e instanceof BlogInvitationReceivedEvent) {
 				BlogInvitationReceivedEvent event =
 						(BlogInvitationReceivedEvent) e;
-				eventWaiter.assertEquals(contactId1, event.getContactId());
+				eventWaiter.assertEquals(contactId1From0, event.getContactId());
 				Blog b = event.getShareable();
 				try {
-					Contact c = contactManager0.getContact(contactId1);
+					Contact c = contactManager0.getContact(contactId1From0);
 					blogSharingManager0.respondToInvitation(b, c, true);
 				} catch (DbException ex) {
 					eventWaiter.rethrow(ex);
@@ -580,14 +439,7 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 
 		@Override
 		public void eventOccurred(Event e) {
-			if (e instanceof MessageStateChangedEvent) {
-				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
-				State s = event.getState();
-				if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
-					LOG.info("TEST: Invitee received message");
-					msgWaiter.resume();
-				}
-			} else if (e instanceof BlogInvitationReceivedEvent) {
+			if (e instanceof BlogInvitationReceivedEvent) {
 				BlogInvitationReceivedEvent event =
 						(BlogInvitationReceivedEvent) e;
 				requestReceived = true;
@@ -609,135 +461,19 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest {
 			else if (e instanceof BlogInvitationResponseReceivedEvent) {
 				BlogInvitationResponseReceivedEvent event =
 						(BlogInvitationResponseReceivedEvent) e;
-				eventWaiter.assertEquals(contactId01, event.getContactId());
+				eventWaiter.assertEquals(contactId0From1, event.getContactId());
 				eventWaiter.resume();
 			}
 		}
 	}
 
-	private void startLifecycles() throws InterruptedException {
-		// Start the lifecycle manager and wait for it to finish
-		lifecycleManager0 = t0.getLifecycleManager();
-		lifecycleManager1 = t1.getLifecycleManager();
-		lifecycleManager2 = t2.getLifecycleManager();
-		lifecycleManager0.startServices(SHARER);
-		lifecycleManager1.startServices(INVITEE);
-		lifecycleManager2.startServices(CONTACT2);
-		lifecycleManager0.waitForStartup();
-		lifecycleManager1.waitForStartup();
-		lifecycleManager2.waitForStartup();
-	}
-
-	private void stopLifecycles() throws InterruptedException {
-		// Clean up
-		lifecycleManager0.stopServices();
-		lifecycleManager1.stopServices();
-		lifecycleManager2.stopServices();
-		lifecycleManager0.waitForShutdown();
-		lifecycleManager1.waitForShutdown();
-		lifecycleManager2.waitForShutdown();
-	}
-
-	private void defaultInit(boolean accept) throws DbException {
-		getDefaultIdentities();
-		addDefaultContacts();
-		getPersonalBlogOfSharer();
-		listenToEvents(accept);
-	}
-
-	private void getDefaultIdentities() throws DbException {
-		author0 = identityManager0.getLocalAuthor();
-		author1 = identityManager1.getLocalAuthor();
-		author2 = identityManager2.getLocalAuthor();
-	}
-
-	private void addDefaultContacts() throws DbException {
-		// sharer adds invitee as contact
-		contactId1 = contactManager0.addContact(author1,
-				author0.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		contact1 = contactManager0.getContact(contactId1);
-		// sharer adds second contact
-		ContactId contactId2 = contactManager0.addContact(author2,
-				author0.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		contact2 = contactManager0.getContact(contactId2);
-		// contacts add sharer back
-		contactId01 = contactManager1.addContact(author0,
-				author1.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		contact01 = contactManager1.getContact(contactId01);
-		ContactId contactId02 = contactManager2.addContact(author0,
-				author2.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		contact02 = contactManager2.getContact(contactId02);
-	}
-
-	private void getPersonalBlogOfSharer() throws DbException {
-		blog0 = blogManager0.getPersonalBlog(author0);
-		blog1 = blogManager0.getPersonalBlog(author1);
-		blog2 = blogManager0.getPersonalBlog(author2);
-	}
-
-	private void listenToEvents(boolean accept) {
+	private void listenToEvents(boolean accept) throws DbException {
 		listener0 = new SharerListener();
-		t0.getEventBus().addListener(listener0);
+		c0.getEventBus().addListener(listener0);
 		listener1 = new InviteeListener(accept);
-		t1.getEventBus().addListener(listener1);
+		c1.getEventBus().addListener(listener1);
 		SharerListener listener2 = new SharerListener();
-		t2.getEventBus().addListener(listener2);
-	}
-
-	private void sync0To1() throws IOException, TimeoutException {
-		deliverMessage(sync0, contactId01, sync1, contactId1,
-				"Sharer to Invitee");
-	}
-
-	private void sync1To0() throws IOException, TimeoutException {
-		deliverMessage(sync1, contactId1, sync0, contactId01,
-				"Invitee to Sharer");
-	}
-
-	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
-			SyncSessionFactory toSync, ContactId toId, String debug)
-			throws IOException, TimeoutException {
-
-		if (debug != null) LOG.info("TEST: Sending message from " + debug);
-
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		// Create an outgoing sync session
-		SyncSession sessionFrom =
-				fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
-		// Write whatever needs to be written
-		sessionFrom.run();
-		out.close();
-
-		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-		// Create an incoming sync session
-		SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
-		// Read whatever needs to be read
-		sessionTo.run();
-		in.close();
-
-		// wait for message to actually arrive
-		msgWaiter.await(TIMEOUT, 1);
-	}
-
-	private void injectEagerSingletons(
-			BlogSharingIntegrationTestComponent component) {
-
-		component.inject(new LifecycleModule.EagerSingletons());
-		component.inject(new BlogsModule.EagerSingletons());
-		component.inject(new CryptoModule.EagerSingletons());
-		component.inject(new ContactModule.EagerSingletons());
-		component.inject(new TransportModule.EagerSingletons());
-		component.inject(new SharingModule.EagerSingletons());
-		component.inject(new SyncModule.EagerSingletons());
-		component.inject(new PropertiesModule.EagerSingletons());
+		c2.getEventBus().addListener(listener2);
 	}
 
 }
diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTestComponent.java
deleted file mode 100644
index d1cde03d4242cb388436625bb01112914b79661f..0000000000000000000000000000000000000000
--- a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTestComponent.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.briarproject;
-
-import org.briarproject.api.blogs.BlogManager;
-import org.briarproject.api.blogs.BlogSharingManager;
-import org.briarproject.api.clients.MessageTracker;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.event.EventBus;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.blogs.BlogsModule;
-import org.briarproject.clients.ClientsModule;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.data.DataModule;
-import org.briarproject.db.DatabaseModule;
-import org.briarproject.event.EventModule;
-import org.briarproject.forum.ForumModule;
-import org.briarproject.identity.IdentityModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.messaging.MessagingModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sharing.SharingModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.system.SystemModule;
-import org.briarproject.transport.TransportModule;
-
-import javax.inject.Singleton;
-
-import dagger.Component;
-
-@Singleton
-@Component(modules = {
-		TestDatabaseModule.class,
-		TestPluginsModule.class,
-		TestSeedProviderModule.class,
-		ClientsModule.class,
-		ContactModule.class,
-		CryptoModule.class,
-		DataModule.class,
-		DatabaseModule.class,
-		EventModule.class,
-		BlogsModule.class,
-		ForumModule.class,
-		IdentityModule.class,
-		LifecycleModule.class,
-		PropertiesModule.class,
-		SharingModule.class,
-		SyncModule.class,
-		SystemModule.class,
-		TransportModule.class,
-		MessagingModule.class
-})
-interface BlogSharingIntegrationTestComponent {
-
-	void inject(BlogSharingIntegrationTest testCase);
-
-	void inject(ContactModule.EagerSingletons init);
-
-	void inject(CryptoModule.EagerSingletons init);
-
-	void inject(BlogsModule.EagerSingletons init);
-
-	void inject(LifecycleModule.EagerSingletons init);
-
-	void inject(PropertiesModule.EagerSingletons init);
-
-	void inject(SharingModule.EagerSingletons init);
-
-	void inject(SyncModule.EagerSingletons init);
-
-	void inject(TransportModule.EagerSingletons init);
-
-	LifecycleManager getLifecycleManager();
-
-	EventBus getEventBus();
-
-	IdentityManager getIdentityManager();
-
-	ContactManager getContactManager();
-
-	BlogSharingManager getBlogSharingManager();
-
-	BlogManager getBlogManager();
-
-	MessageTracker getMessageTracker();
-
-	SyncSessionFactory getSyncSessionFactory();
-
-}
diff --git a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java
index 3436fe962ec9c22d37033c876c83f6ae3a1c2b23..8b391c28c46a3633507cff5d0403e93d09871430 100644
--- a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java
@@ -1,14 +1,365 @@
 package org.briarproject;
 
+import android.support.annotation.CallSuper;
+
+import net.jodah.concurrentunit.Waiter;
+
+import org.briarproject.api.blogs.BlogFactory;
+import org.briarproject.api.blogs.BlogPostFactory;
+import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.clients.MessageTracker;
 import org.briarproject.api.clients.MessageTracker.GroupCount;
+import org.briarproject.api.contact.Contact;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventListener;
+import org.briarproject.api.event.MessageStateChangedEvent;
+import org.briarproject.api.forum.ForumPostFactory;
+import org.briarproject.api.identity.AuthorFactory;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
+import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
+import org.briarproject.api.privategroup.GroupMessageFactory;
+import org.briarproject.api.privategroup.PrivateGroupFactory;
+import org.briarproject.api.privategroup.invitation.GroupInvitationFactory;
 import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.SyncSession;
+import org.briarproject.api.sync.SyncSessionFactory;
+import org.briarproject.api.system.Clock;
+import org.briarproject.blogs.BlogsModule;
+import org.briarproject.contact.ContactModule;
+import org.briarproject.crypto.CryptoModule;
+import org.briarproject.forum.ForumModule;
+import org.briarproject.introduction.IntroductionGroupFactory;
+import org.briarproject.introduction.IntroductionModule;
+import org.briarproject.lifecycle.LifecycleModule;
+import org.briarproject.privategroup.PrivateGroupModule;
+import org.briarproject.properties.PropertiesModule;
+import org.briarproject.sharing.SharingModule;
+import org.briarproject.sync.SyncModule;
+import org.briarproject.transport.TransportModule;
+import org.jetbrains.annotations.Nullable;
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
 
+import static org.briarproject.TestPluginsModule.MAX_LATENCY;
+import static org.briarproject.TestUtils.getSecretKey;
+import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
+import static org.briarproject.api.sync.ValidationManager.State.INVALID;
+import static org.briarproject.api.sync.ValidationManager.State.PENDING;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+@MethodsNotNullByDefault
+@ParametersNotNullByDefault
 public abstract class BriarIntegrationTest extends BriarTestCase {
 
+	private static final Logger LOG =
+			Logger.getLogger(BriarIntegrationTest.class.getName());
+
+	@Nullable
+	protected ContactId contactId1From2, contactId2From1;
+	protected ContactId contactId0From1, contactId0From2, contactId1From0,
+			contactId2From0;
+	protected Contact contact0From1, contact0From2, contact1From0,
+			contact2From0;
+	protected LocalAuthor author0, author1, author2;
+	protected ContactManager contactManager0, contactManager1, contactManager2;
+	protected IdentityManager identityManager0, identityManager1,
+			identityManager2;
+	protected DatabaseComponent db0, db1, db2;
+	protected MessageTracker messageTracker0, messageTracker1, messageTracker2;
+
+	private LifecycleManager lifecycleManager0, lifecycleManager1,
+			lifecycleManager2;
+	private SyncSessionFactory sync0, sync1, sync2;
+
+	@Inject
+	protected Clock clock;
+	@Inject
+	protected CryptoComponent crypto;
+	@Inject
+	protected ClientHelper clientHelper;
+	@Inject
+	protected AuthorFactory authorFactory;
+	@Inject
+	protected IntroductionGroupFactory introductionGroupFactory;
+	@Inject
+	ContactGroupFactory contactGroupFactory;
+	@Inject
+	PrivateGroupFactory privateGroupFactory;
+	@Inject
+	GroupMessageFactory groupMessageFactory;
+	@Inject
+	GroupInvitationFactory groupInvitationFactory;
+	@Inject
+	BlogFactory blogFactory;
+	@Inject
+	BlogPostFactory blogPostFactory;
+	@Inject
+	ForumPostFactory forumPostFactory;
+
+	// objects accessed from background threads need to be volatile
+	private volatile Waiter validationWaiter;
+	private volatile Waiter deliveryWaiter;
+
+	protected final static int TIMEOUT = 15000;
+	protected BriarIntegrationTestComponent c0, c1, c2;
+
+	private final File testDir = TestUtils.getTestDirectory();
+	private final String AUTHOR0 = "Author 0";
+	private final String AUTHOR1 = "Author 1";
+	private final String AUTHOR2 = "Author 2";
+
+	@Before
+	@CallSuper
+	public void setUp() throws Exception {
+		BriarIntegrationTestComponent component =
+				DaggerBriarIntegrationTestComponent.builder().build();
+		component.inject(this);
+
+		assertTrue(testDir.mkdirs());
+		File t0Dir = new File(testDir, AUTHOR0);
+		c0 = DaggerBriarIntegrationTestComponent.builder()
+				.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
+		injectEagerSingletons(c0);
+		File t1Dir = new File(testDir, AUTHOR1);
+		c1 = DaggerBriarIntegrationTestComponent.builder()
+				.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
+		injectEagerSingletons(c1);
+		File t2Dir = new File(testDir, AUTHOR2);
+		c2 = DaggerBriarIntegrationTestComponent.builder()
+				.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
+		injectEagerSingletons(c2);
+
+		identityManager0 = c0.getIdentityManager();
+		identityManager1 = c1.getIdentityManager();
+		identityManager2 = c2.getIdentityManager();
+		contactManager0 = c0.getContactManager();
+		contactManager1 = c1.getContactManager();
+		contactManager2 = c2.getContactManager();
+		messageTracker0 = c0.getMessageTracker();
+		messageTracker1 = c1.getMessageTracker();
+		messageTracker2 = c2.getMessageTracker();
+		db0 = c0.getDatabaseComponent();
+		db1 = c1.getDatabaseComponent();
+		db2 = c2.getDatabaseComponent();
+		sync0 = c0.getSyncSessionFactory();
+		sync1 = c1.getSyncSessionFactory();
+		sync2 = c2.getSyncSessionFactory();
+
+		// initialize waiters fresh for each test
+		validationWaiter = new Waiter();
+		deliveryWaiter = new Waiter();
+
+		startLifecycles();
+
+		getDefaultIdentities();
+		addDefaultContacts();
+		listenToEvents();
+	}
+
+	private void injectEagerSingletons(
+			BriarIntegrationTestComponent component) {
+		component.inject(new LifecycleModule.EagerSingletons());
+		component.inject(new BlogsModule.EagerSingletons());
+		component.inject(new CryptoModule.EagerSingletons());
+		component.inject(new ContactModule.EagerSingletons());
+		component.inject(new ForumModule.EagerSingletons());
+		component.inject(new IntroductionModule.EagerSingletons());
+		component.inject(new PropertiesModule.EagerSingletons());
+		component.inject(new PrivateGroupModule.EagerSingletons());
+		component.inject(new SyncModule.EagerSingletons());
+		component.inject(new SharingModule.EagerSingletons());
+		component.inject(new TransportModule.EagerSingletons());
+	}
+
+	private void startLifecycles() throws InterruptedException {
+		// Start the lifecycle manager and wait for it to finish
+		lifecycleManager0 = c0.getLifecycleManager();
+		lifecycleManager1 = c1.getLifecycleManager();
+		lifecycleManager2 = c2.getLifecycleManager();
+		lifecycleManager0.startServices(AUTHOR0);
+		lifecycleManager1.startServices(AUTHOR1);
+		lifecycleManager2.startServices(AUTHOR2);
+		lifecycleManager0.waitForStartup();
+		lifecycleManager1.waitForStartup();
+		lifecycleManager2.waitForStartup();
+	}
+
+	private void listenToEvents() {
+		Listener listener0 = new Listener();
+		c0.getEventBus().addListener(listener0);
+		Listener listener1 = new Listener();
+		c1.getEventBus().addListener(listener1);
+		Listener listener2 = new Listener();
+		c2.getEventBus().addListener(listener2);
+	}
+
+	private class Listener implements EventListener {
+		@Override
+		public void eventOccurred(Event e) {
+			if (e instanceof MessageStateChangedEvent) {
+				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
+				if (!event.isLocal()) {
+					if (event.getState() == DELIVERED) {
+						LOG.info("Delivered new message");
+						deliveryWaiter.resume();
+					} else if (event.getState() == INVALID ||
+							event.getState() == PENDING) {
+						LOG.info("Validated new " + event.getState().name() +
+								" message");
+						validationWaiter.resume();
+					}
+				}
+			}
+		}
+	}
+
+	private void getDefaultIdentities() throws DbException {
+		author0 = identityManager0.getLocalAuthor();
+		author1 = identityManager1.getLocalAuthor();
+		author2 = identityManager2.getLocalAuthor();
+	}
+
+	protected void addDefaultContacts() throws DbException {
+		contactId1From0 = contactManager0
+				.addContact(author1, author0.getId(), getSecretKey(),
+						clock.currentTimeMillis(), true, true, true);
+		contact1From0 = contactManager0.getContact(contactId1From0);
+		contactId0From1 = contactManager1
+				.addContact(author0, author1.getId(), getSecretKey(),
+						clock.currentTimeMillis(), true, true, true);
+		contact0From1 = contactManager1.getContact(contactId0From1);
+		contactId2From0 = contactManager0
+				.addContact(author2, author0.getId(), getSecretKey(),
+						clock.currentTimeMillis(), true, true, true);
+		contact2From0 = contactManager0.getContact(contactId2From0);
+		contactId0From2 = contactManager2
+				.addContact(author0, author2.getId(), getSecretKey(),
+						clock.currentTimeMillis(), true, true, true);
+		contact0From2 = contactManager2.getContact(contactId0From2);
+	}
+
+	protected void addContacts1And2() throws DbException {
+		contactId2From1 = contactManager1
+				.addContact(author2, author1.getId(), getSecretKey(),
+						clock.currentTimeMillis(), true, true, true);
+		contactId1From2 = contactManager2
+				.addContact(author1, author2.getId(), getSecretKey(),
+						clock.currentTimeMillis(), true, true, true);
+	}
+
+	@After
+	public void tearDown() throws Exception {
+		stopLifecycles();
+		TestUtils.deleteTestDirectory(testDir);
+	}
+
+	private void stopLifecycles() throws InterruptedException {
+		// Clean up
+		lifecycleManager0.stopServices();
+		lifecycleManager1.stopServices();
+		lifecycleManager2.stopServices();
+		lifecycleManager0.waitForShutdown();
+		lifecycleManager1.waitForShutdown();
+		lifecycleManager2.waitForShutdown();
+	}
+
+	protected void sync0To1(int num, boolean valid)
+			throws IOException, TimeoutException {
+		syncMessage(sync0, contactId0From1, sync1, contactId1From0, num, valid);
+	}
+
+	protected void sync0To2(int num, boolean valid)
+			throws IOException, TimeoutException {
+		syncMessage(sync0, contactId0From2, sync2, contactId2From0, num, valid);
+	}
+
+	protected void sync1To0(int num, boolean valid)
+			throws IOException, TimeoutException {
+		syncMessage(sync1, contactId1From0, sync0, contactId0From1, num, valid);
+	}
+
+	protected void sync2To0(int num, boolean valid)
+			throws IOException, TimeoutException {
+		syncMessage(sync2, contactId2From0, sync0, contactId0From2, num, valid);
+	}
+
+	protected void sync2To1(int num, boolean valid)
+			throws IOException, TimeoutException {
+		assertTrue(contactId2From1 != null);
+		assertTrue(contactId1From2 != null);
+		syncMessage(sync2, contactId2From1, sync1, contactId1From2, num, valid);
+	}
+
+	protected void sync1To2(int num, boolean valid)
+			throws IOException, TimeoutException {
+		assertTrue(contactId2From1 != null);
+		assertTrue(contactId1From2 != null);
+		syncMessage(sync1, contactId1From2, sync2, contactId2From1, num, valid);
+	}
+
+	private void syncMessage(SyncSessionFactory fromSync, ContactId fromId,
+			SyncSessionFactory toSync, ContactId toId, int num, boolean valid)
+			throws IOException, TimeoutException {
+
+		// Debug output
+		String from = "0";
+		if (fromSync == sync1) from = "1";
+		else if (fromSync == sync2) from = "2";
+		String to = "0";
+		if (toSync == sync1) to = "1";
+		else if (toSync == sync2) to = "2";
+		LOG.info("TEST: Sending message from " + from + " to " + to);
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		// Create an outgoing sync session
+		SyncSession sessionFrom =
+				fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
+		// Write whatever needs to be written
+		sessionFrom.run();
+		out.close();
+
+		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+		// Create an incoming sync session
+		SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
+		// Read whatever needs to be read
+		sessionTo.run();
+		in.close();
+
+		if (valid) {
+			deliveryWaiter.await(TIMEOUT, num);
+		} else {
+			validationWaiter.await(TIMEOUT, num);
+		}
+	}
+
+	protected void removeAllContacts() throws DbException {
+		contactManager0.removeContact(contactId1From0);
+		contactManager0.removeContact(contactId2From0);
+		contactManager1.removeContact(contactId0From1);
+		contactManager1.removeContact(contactId2From1);
+		contactManager2.removeContact(contactId0From2);
+		contactManager2.removeContact(contactId1From2);
+	}
+
 	protected static void assertGroupCount(MessageTracker tracker, GroupId g,
 			long msgCount, long unreadCount, long latestMsg)
 			throws DbException {
diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java
similarity index 67%
rename from briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTestComponent.java
rename to briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java
index 88b058807ce19031ffa8f311ffb8c42c2cd55708..835c345bd758afd9684e936fc421b6fb29b815b4 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTestComponent.java
+++ b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java
@@ -1,15 +1,20 @@
 package org.briarproject;
 
+import org.briarproject.api.blogs.BlogManager;
+import org.briarproject.api.blogs.BlogSharingManager;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.MessageQueueManager;
-import org.briarproject.api.clients.ContactGroupFactory;
+import org.briarproject.api.clients.MessageTracker;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumSharingManager;
 import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.introduction.IntroductionManager;
 import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.sync.SyncSessionFactory;
 import org.briarproject.blogs.BlogsModule;
 import org.briarproject.clients.ClientsModule;
@@ -20,8 +25,12 @@ import org.briarproject.db.DatabaseModule;
 import org.briarproject.event.EventModule;
 import org.briarproject.forum.ForumModule;
 import org.briarproject.identity.IdentityModule;
+import org.briarproject.introduction.IntroductionModule;
+import org.briarproject.introduction.MessageSender;
 import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.messaging.MessagingModule;
+import org.briarproject.privategroup.PrivateGroupModule;
+import org.briarproject.privategroup.invitation.GroupInvitationModule;
 import org.briarproject.properties.PropertiesModule;
 import org.briarproject.sharing.SharingModule;
 import org.briarproject.sync.SyncModule;
@@ -40,23 +49,28 @@ import dagger.Component;
 		ClientsModule.class,
 		ContactModule.class,
 		CryptoModule.class,
+		BlogsModule.class,
 		DataModule.class,
 		DatabaseModule.class,
 		EventModule.class,
 		ForumModule.class,
-		BlogsModule.class,
+		GroupInvitationModule.class,
+		MessagingModule.class,
 		IdentityModule.class,
+		IntroductionModule.class,
 		LifecycleModule.class,
+		PrivateGroupModule.class,
 		PropertiesModule.class,
 		SharingModule.class,
 		SyncModule.class,
 		SystemModule.class,
-		TransportModule.class,
-		MessagingModule.class
+		TransportModule.class
 })
-interface ForumSharingIntegrationTestComponent {
+public interface BriarIntegrationTestComponent {
+
+	void inject(BriarIntegrationTest init);
 
-	void inject(ForumSharingIntegrationTest testCase);
+	void inject(BlogsModule.EagerSingletons init);
 
 	void inject(ContactModule.EagerSingletons init);
 
@@ -64,8 +78,12 @@ interface ForumSharingIntegrationTestComponent {
 
 	void inject(ForumModule.EagerSingletons init);
 
+	void inject(IntroductionModule.EagerSingletons init);
+
 	void inject(LifecycleModule.EagerSingletons init);
 
+	void inject(PrivateGroupModule.EagerSingletons init);
+
 	void inject(PropertiesModule.EagerSingletons init);
 
 	void inject(SharingModule.EagerSingletons init);
@@ -80,22 +98,32 @@ interface ForumSharingIntegrationTestComponent {
 
 	IdentityManager getIdentityManager();
 
+	ClientHelper getClientHelper();
+
 	ContactManager getContactManager();
 
+	SyncSessionFactory getSyncSessionFactory();
+
+	DatabaseComponent getDatabaseComponent();
+
+	BlogManager getBlogManager();
+
+	BlogSharingManager getBlogSharingManager();
+
 	ForumSharingManager getForumSharingManager();
 
 	ForumManager getForumManager();
 
-	SyncSessionFactory getSyncSessionFactory();
+	IntroductionManager getIntroductionManager();
 
-	/* the following methods are only needed to manually construct messages */
+	MessageTracker getMessageTracker();
 
-	DatabaseComponent getDatabaseComponent();
+	MessageSender getMessageSender();
 
-	ContactGroupFactory getContactGroupFactory();
+	MessageQueueManager getMessageQueueManager();
 
-	ClientHelper getClientHelper();
+	PrivateGroupManager getPrivateGroupManager();
 
-	MessageQueueManager getMessageQueueManager();
+	TransportPropertyManager getTransportPropertyManager();
 
 }
diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java
index e574d224ae38278d9edd50ee43d0fbf4c50d4658..f835ea5305128fdad5996562a57519d91bfbb65d 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java
@@ -2,135 +2,47 @@ package org.briarproject;
 
 import junit.framework.Assert;
 
-import net.jodah.concurrentunit.Waiter;
-
-import org.briarproject.api.clients.MessageTracker;
-import org.briarproject.api.contact.Contact;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.crypto.SecretKey;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.event.Event;
-import org.briarproject.api.event.EventListener;
-import org.briarproject.api.event.MessageStateChangedEvent;
 import org.briarproject.api.forum.Forum;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumPost;
-import org.briarproject.api.forum.ForumPostFactory;
 import org.briarproject.api.forum.ForumPostHeader;
 import org.briarproject.api.forum.ForumSharingManager;
-import org.briarproject.api.identity.AuthorFactory;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.identity.LocalAuthor;
-import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sync.GroupId;
-import org.briarproject.api.sync.SyncSession;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.api.system.Clock;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.forum.ForumModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sharing.SharingModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.transport.TransportModule;
 import org.jetbrains.annotations.Nullable;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
 import java.util.Collection;
-import java.util.concurrent.TimeoutException;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertFalse;
-import static org.briarproject.TestPluginsModule.MAX_LATENCY;
-import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
-import static org.briarproject.api.sync.ValidationManager.State.INVALID;
-import static org.briarproject.api.sync.ValidationManager.State.PENDING;
 import static org.junit.Assert.assertTrue;
 
 public class ForumManagerTest extends BriarIntegrationTest {
 
-	private LifecycleManager lifecycleManager0, lifecycleManager1;
-	private SyncSessionFactory sync0, sync1;
 	private ForumManager forumManager0, forumManager1;
-	private ContactManager contactManager0, contactManager1;
-	private MessageTracker messageTracker0, messageTracker1;
-	private ContactId contactId0,contactId1;
-	private IdentityManager identityManager0, identityManager1;
-	private LocalAuthor author0, author1;
+	private ForumSharingManager forumSharingManager0, forumSharingManager1;
 	private Forum forum0;
-
-	@Inject
-	Clock clock;
-	@Inject
-	AuthorFactory authorFactory;
-	@Inject
-	CryptoComponent crypto;
-	@Inject
-	ForumPostFactory forumPostFactory;
-
-	// objects accessed from background threads need to be volatile
-	private volatile ForumSharingManager forumSharingManager0;
-	private volatile ForumSharingManager forumSharingManager1;
-	private volatile Waiter validationWaiter;
-	private volatile Waiter deliveryWaiter;
-
-	private final File testDir = TestUtils.getTestDirectory();
-	private final SecretKey master = TestUtils.getSecretKey();
-	private final int TIMEOUT = 15000;
-	private final String SHARER = "Sharer";
-	private final String INVITEE = "Invitee";
-
-	private static final Logger LOG =
-			Logger.getLogger(ForumSharingIntegrationTest.class.getName());
-
-	private ForumManagerTestComponent t0, t1;
+	private GroupId g;
 
 	@Before
 	public void setUp() throws Exception {
-		ForumManagerTestComponent component =
-				DaggerForumManagerTestComponent.builder().build();
-		component.inject(this);
-		injectEagerSingletons(component);
+		super.setUp();
 
-		assertTrue(testDir.mkdirs());
-		File t0Dir = new File(testDir, SHARER);
-		t0 = DaggerForumManagerTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
-		injectEagerSingletons(t0);
-		File t1Dir = new File(testDir, INVITEE);
-		t1 = DaggerForumManagerTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
-		injectEagerSingletons(t1);
+		forumManager0 = c0.getForumManager();
+		forumManager1 = c1.getForumManager();
+		forumSharingManager0 = c0.getForumSharingManager();
+		forumSharingManager1 = c1.getForumSharingManager();
 
-		identityManager0 = t0.getIdentityManager();
-		identityManager1 = t1.getIdentityManager();
-		contactManager0 = t0.getContactManager();
-		contactManager1 = t1.getContactManager();
-		messageTracker0 = t0.getMessageTracker();
-		messageTracker1 = t1.getMessageTracker();
-		forumManager0 = t0.getForumManager();
-		forumManager1 = t1.getForumManager();
-		forumSharingManager0 = t0.getForumSharingManager();
-		forumSharingManager1 = t1.getForumSharingManager();
-		sync0 = t0.getSyncSessionFactory();
-		sync1 = t1.getSyncSessionFactory();
 
-		// initialize waiters fresh for each test
-		validationWaiter = new Waiter();
-		deliveryWaiter = new Waiter();
+		forum0 = forumManager0.addForum("Test Forum");
+		g = forum0.getId();
+		// share forum
+		forumSharingManager0.sendInvitation(g, contactId1From0, null);
+		sync0To1(1, true);
+		forumSharingManager1.respondToInvitation(forum0, contact0From1, true);
+		sync1To0(1, true);
 	}
 
 	private ForumPost createForumPost(GroupId groupId,
@@ -142,36 +54,33 @@ public class ForumManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testForumPost() throws Exception {
-		startLifecycles();
-		getDefaultIdentities();
-		Forum forum = forumManager0.addForum("TestForum");
 		assertEquals(1, forumManager0.getForums().size());
 		final long ms1 = clock.currentTimeMillis() - 1000L;
 		final String body1 = "some forum text";
 		final long ms2 = clock.currentTimeMillis();
 		final String body2 = "some other forum text";
 		ForumPost post1 =
-				createForumPost(forum.getGroup().getId(), null, body1, ms1);
+				createForumPost(forum0.getGroup().getId(), null, body1, ms1);
 		assertEquals(ms1, post1.getMessage().getTimestamp());
 		ForumPost post2 =
-				createForumPost(forum.getGroup().getId(), post1, body2, ms2);
+				createForumPost(forum0.getGroup().getId(), post1, body2, ms2);
 		assertEquals(ms2, post2.getMessage().getTimestamp());
 		forumManager0.addLocalPost(post1);
-		forumManager0.setReadFlag(forum.getGroup().getId(),
+		forumManager0.setReadFlag(forum0.getGroup().getId(),
 				post1.getMessage().getId(), true);
-		assertGroupCount(messageTracker0, forum.getGroup().getId(), 1, 0,
+		assertGroupCount(messageTracker0, forum0.getGroup().getId(), 1, 0,
 				post1.getMessage().getTimestamp());
 		forumManager0.addLocalPost(post2);
-		forumManager0.setReadFlag(forum.getGroup().getId(),
+		forumManager0.setReadFlag(forum0.getGroup().getId(),
 				post2.getMessage().getId(), false);
-		assertGroupCount(messageTracker0, forum.getGroup().getId(), 2, 1,
+		assertGroupCount(messageTracker0, forum0.getGroup().getId(), 2, 1,
 				post2.getMessage().getTimestamp());
-		forumManager0.setReadFlag(forum.getGroup().getId(),
+		forumManager0.setReadFlag(forum0.getGroup().getId(),
 				post2.getMessage().getId(), false);
-		assertGroupCount(messageTracker0, forum.getGroup().getId(), 2, 1,
+		assertGroupCount(messageTracker0, forum0.getGroup().getId(), 2, 1,
 				post2.getMessage().getTimestamp());
 		Collection<ForumPostHeader> headers =
-				forumManager0.getPostHeaders(forum.getGroup().getId());
+				forumManager0.getPostHeaders(forum0.getGroup().getId());
 		assertEquals(2, headers.size());
 		for (ForumPostHeader h : headers) {
 			final String hBody = forumManager0.getPostBody(h.getId());
@@ -192,26 +101,12 @@ public class ForumManagerTest extends BriarIntegrationTest {
 				assertFalse(h.isRead());
 			}
 		}
-		forumManager0.removeForum(forum);
+		forumManager0.removeForum(forum0);
 		assertEquals(0, forumManager0.getForums().size());
-		stopLifecycles();
 	}
 
 	@Test
 	public void testForumPostDelivery() throws Exception {
-		startLifecycles();
-		defaultInit();
-
-		// share forum
-		GroupId g = forum0.getId();
-		forumSharingManager0.sendInvitation(g, contactId1, null);
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
-		Contact c0 = contactManager1.getContact(contactId0);
-		forumSharingManager1.respondToInvitation(forum0, c0, true);
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 1);
-
 		// add one forum post
 		long time = clock.currentTimeMillis();
 		ForumPost post1 = createForumPost(g, null, "a", time);
@@ -222,8 +117,7 @@ public class ForumManagerTest extends BriarIntegrationTest {
 		assertGroupCount(messageTracker1, g, 0, 0, 0);
 
 		// send post to 1
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync0To1(1, true);
 		assertEquals(1, forumManager1.getPostHeaders(g).size());
 		assertGroupCount(messageTracker1, g, 1, 1, time);
 
@@ -237,29 +131,13 @@ public class ForumManagerTest extends BriarIntegrationTest {
 		assertGroupCount(messageTracker1, g, 2, 1, time2);
 
 		// send post to 0
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync1To0(1, true);
 		assertEquals(2, forumManager1.getPostHeaders(g).size());
 		assertGroupCount(messageTracker0, g, 2, 1, time2);
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testForumPostDeliveredAfterParent() throws Exception {
-		startLifecycles();
-		defaultInit();
-
-		// share forum
-		GroupId g = forum0.getId();
-		forumSharingManager0.sendInvitation(g, contactId1, null);
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
-		Contact c0 = contactManager1.getContact(contactId0);
-		forumSharingManager1.respondToInvitation(forum0, c0, true);
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 1);
-
 		// add one forum post without the parent
 		long time = clock.currentTimeMillis();
 		ForumPost post1 = createForumPost(g, null, "a", time);
@@ -269,8 +147,7 @@ public class ForumManagerTest extends BriarIntegrationTest {
 		assertEquals(0, forumManager1.getPostHeaders(g).size());
 
 		// send post to 1 without waiting for message delivery
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 		assertEquals(0, forumManager1.getPostHeaders(g).size());
 
 		// now add the parent post as well
@@ -279,37 +156,19 @@ public class ForumManagerTest extends BriarIntegrationTest {
 		assertEquals(0, forumManager1.getPostHeaders(g).size());
 
 		// and send it over to 1 and wait for a second message to be delivered
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 2);
+		sync0To1(2, true);
 		assertEquals(2, forumManager1.getPostHeaders(g).size());
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testForumPostWithParentInOtherGroup() throws Exception {
-		startLifecycles();
-		defaultInit();
-
-		// share forum
-		GroupId g = forum0.getId();
-		forumSharingManager0.sendInvitation(g, contactId1, null);
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
-		Contact c0 = contactManager1.getContact(contactId0);
-		forumSharingManager1.respondToInvitation(forum0, c0, true);
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 1);
-
 		// share a second forum
 		Forum forum1 = forumManager0.addForum("Test Forum1");
 		GroupId g1 = forum1.getId();
-		forumSharingManager0.sendInvitation(g1, contactId1, null);
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
-		forumSharingManager1.respondToInvitation(forum1, c0, true);
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 1);
+		forumSharingManager0.sendInvitation(g1, contactId1From0, null);
+		sync0To1(1, true);
+		forumSharingManager1.respondToInvitation(forum1, contact0From1, true);
+		sync1To0(1, true);
 
 		// add one forum post with a parent in another forum
 		long time = clock.currentTimeMillis();
@@ -320,8 +179,7 @@ public class ForumManagerTest extends BriarIntegrationTest {
 		assertEquals(0, forumManager1.getPostHeaders(g).size());
 
 		// send the child post to 1
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 		assertEquals(1, forumManager0.getPostHeaders(g).size());
 		assertEquals(0, forumManager1.getPostHeaders(g).size());
 
@@ -331,132 +189,12 @@ public class ForumManagerTest extends BriarIntegrationTest {
 		assertEquals(0, forumManager1.getPostHeaders(g1).size());
 
 		// send posts to 1
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync0To1(1, true);
 		assertEquals(1, forumManager0.getPostHeaders(g).size());
 		assertEquals(1, forumManager0.getPostHeaders(g1).size());
 		// the next line is critical, makes sure post doesn't show up
 		assertEquals(0, forumManager1.getPostHeaders(g).size());
 		assertEquals(1, forumManager1.getPostHeaders(g1).size());
-
-		stopLifecycles();
-	}
-
-	@After
-	public void tearDown() throws Exception {
-		TestUtils.deleteTestDirectory(testDir);
-	}
-
-	private class Listener implements EventListener {
-		@Override
-		public void eventOccurred(Event e) {
-			if (e instanceof MessageStateChangedEvent) {
-				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
-				if (!event.isLocal()) {
-					if (event.getState() == DELIVERED) {
-						deliveryWaiter.resume();
-					} else if (event.getState() == INVALID ||
-							event.getState() == PENDING) {
-						validationWaiter.resume();
-					}
-				}
-			}
-		}
-	}
-
-	private void defaultInit() throws DbException {
-		getDefaultIdentities();
-		addDefaultContacts();
-		addForum();
-		listenToEvents();
-	}
-
-	private void getDefaultIdentities() throws DbException {
-		author0 = identityManager0.getLocalAuthor();
-		author1 = identityManager1.getLocalAuthor();
-	}
-
-	private void addDefaultContacts() throws DbException {
-		// sharer adds invitee as contact
-		contactId1 = contactManager0.addContact(author1,
-				author0.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		// invitee adds sharer back
-		contactId0 = contactManager1.addContact(author0,
-				author1.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-	}
-
-	private void addForum() throws DbException {
-		forum0 = forumManager0.addForum("Test Forum");
-	}
-
-	private void listenToEvents() {
-		Listener listener0 = new Listener();
-		t0.getEventBus().addListener(listener0);
-		Listener listener1 = new Listener();
-		t1.getEventBus().addListener(listener1);
-	}
-
-	private void sync0To1() throws IOException, TimeoutException {
-		deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-	}
-
-	private void sync1To0() throws IOException, TimeoutException {
-		deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
-	}
-
-	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
-			SyncSessionFactory toSync, ContactId toId, String debug)
-			throws IOException, TimeoutException {
-
-		if (debug != null) LOG.info("TEST: Sending message from " + debug);
-
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		// Create an outgoing sync session
-		SyncSession sessionFrom =
-				fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
-		// Write whatever needs to be written
-		sessionFrom.run();
-		out.close();
-
-		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-		// Create an incoming sync session
-		SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
-		// Read whatever needs to be read
-		sessionTo.run();
-		in.close();
-	}
-
-	private void startLifecycles() throws InterruptedException {
-		// Start the lifecycle manager and wait for it to finish
-		lifecycleManager0 = t0.getLifecycleManager();
-		lifecycleManager1 = t1.getLifecycleManager();
-		lifecycleManager0.startServices(SHARER);
-		lifecycleManager1.startServices(INVITEE);
-		lifecycleManager0.waitForStartup();
-		lifecycleManager1.waitForStartup();
-	}
-
-	private void stopLifecycles() throws InterruptedException {
-		// Clean up
-		lifecycleManager0.stopServices();
-		lifecycleManager1.stopServices();
-		lifecycleManager0.waitForShutdown();
-		lifecycleManager1.waitForShutdown();
-	}
-
-	private void injectEagerSingletons(ForumManagerTestComponent component) {
-		component.inject(new LifecycleModule.EagerSingletons());
-		component.inject(new ForumModule.EagerSingletons());
-		component.inject(new CryptoModule.EagerSingletons());
-		component.inject(new ContactModule.EagerSingletons());
-		component.inject(new TransportModule.EagerSingletons());
-		component.inject(new SharingModule.EagerSingletons());
-		component.inject(new SyncModule.EagerSingletons());
-		component.inject(new PropertiesModule.EagerSingletons());
 	}
 
 }
diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTestComponent.java
deleted file mode 100644
index 24278e4af5e878c5c16e9ff55730fd0fa52044cd..0000000000000000000000000000000000000000
--- a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTestComponent.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.briarproject;
-
-import org.briarproject.api.clients.MessageTracker;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.event.EventBus;
-import org.briarproject.api.forum.ForumManager;
-import org.briarproject.api.forum.ForumSharingManager;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.blogs.BlogsModule;
-import org.briarproject.clients.ClientsModule;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.data.DataModule;
-import org.briarproject.db.DatabaseModule;
-import org.briarproject.event.EventModule;
-import org.briarproject.forum.ForumModule;
-import org.briarproject.identity.IdentityModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.messaging.MessagingModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sharing.SharingModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.system.SystemModule;
-import org.briarproject.transport.TransportModule;
-
-import javax.inject.Singleton;
-
-import dagger.Component;
-
-@Singleton
-@Component(modules = {
-		TestDatabaseModule.class,
-		TestPluginsModule.class,
-		TestSeedProviderModule.class,
-		ClientsModule.class,
-		ContactModule.class,
-		CryptoModule.class,
-		DataModule.class,
-		DatabaseModule.class,
-		EventModule.class,
-		ForumModule.class,
-		BlogsModule.class,
-		IdentityModule.class,
-		LifecycleModule.class,
-		PropertiesModule.class,
-		SharingModule.class,
-		SyncModule.class,
-		SystemModule.class,
-		TransportModule.class,
-		MessagingModule.class
-})
-interface ForumManagerTestComponent {
-
-	void inject(ForumManagerTest testCase);
-
-	void inject(ContactModule.EagerSingletons init);
-
-	void inject(CryptoModule.EagerSingletons init);
-
-	void inject(ForumModule.EagerSingletons init);
-
-	void inject(LifecycleModule.EagerSingletons init);
-
-	void inject(PropertiesModule.EagerSingletons init);
-
-	void inject(SharingModule.EagerSingletons init);
-
-	void inject(SyncModule.EagerSingletons init);
-
-	void inject(TransportModule.EagerSingletons init);
-
-	LifecycleManager getLifecycleManager();
-
-	EventBus getEventBus();
-
-	IdentityManager getIdentityManager();
-
-	ContactManager getContactManager();
-
-	MessageTracker getMessageTracker();
-
-	ForumSharingManager getForumSharingManager();
-
-	ForumManager getForumManager();
-
-	SyncSessionFactory getSyncSessionFactory();
-
-}
diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
index 896e54e694c7cde207b202a707dafdcce322a467..2fd82150705745658259bdf872c114f70ea47961 100644
--- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java
@@ -3,16 +3,10 @@ package org.briarproject;
 import net.jodah.concurrentunit.Waiter;
 
 import org.briarproject.api.Bytes;
-import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.clients.MessageQueueManager;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.Contact;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.data.BdfList;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Metadata;
 import org.briarproject.api.db.Transaction;
@@ -20,894 +14,741 @@ import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.ForumInvitationReceivedEvent;
 import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
-import org.briarproject.api.event.MessageStateChangedEvent;
 import org.briarproject.api.forum.Forum;
 import org.briarproject.api.forum.ForumInvitationRequest;
 import org.briarproject.api.forum.ForumInvitationResponse;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumPost;
-import org.briarproject.api.forum.ForumPostFactory;
 import org.briarproject.api.forum.ForumPostHeader;
 import org.briarproject.api.forum.ForumSharingManager;
-import org.briarproject.api.identity.AuthorFactory;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.identity.LocalAuthor;
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.sharing.SharingInvitationItem;
 import org.briarproject.api.sharing.InvitationMessage;
+import org.briarproject.api.sharing.SharingInvitationItem;
 import org.briarproject.api.sync.Group;
-import org.briarproject.api.sync.SyncSession;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.api.sync.ValidationManager.State;
-import org.briarproject.api.system.Clock;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.forum.ForumModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sharing.SharingModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.transport.TransportModule;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.TimeoutException;
-import java.util.logging.Logger;
 
-import javax.inject.Inject;
-
-import static org.briarproject.TestPluginsModule.MAX_LATENCY;
 import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
 import static org.briarproject.api.forum.ForumSharingManager.CLIENT_ID;
 import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
-import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
-import static org.briarproject.api.sync.ValidationManager.State.INVALID;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-public class ForumSharingIntegrationTest extends BriarTestCase {
+public class ForumSharingIntegrationTest extends BriarIntegrationTest {
 
-	private LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
-	private SyncSessionFactory sync0, sync1, sync2;
 	private ForumManager forumManager0, forumManager1;
-	private ContactManager contactManager0, contactManager1, contactManager2;
-	private ContactId contactId0, contactId2, contactId1, contactId21;
-	private IdentityManager identityManager0, identityManager1, identityManager2;
-	private LocalAuthor author0, author1, author2;
-	private Forum forum0;
 	private SharerListener listener0, listener2;
 	private InviteeListener listener1;
-
-	@Inject
-	Clock clock;
-	@Inject
-	AuthorFactory authorFactory;
-	@Inject
-	ForumPostFactory forumPostFactory;
-	@Inject
-	CryptoComponent cryptoComponent;
+	private Forum forum0;
 
 	// objects accessed from background threads need to be volatile
 	private volatile ForumSharingManager forumSharingManager0;
 	private volatile ForumSharingManager forumSharingManager1;
 	private volatile ForumSharingManager forumSharingManager2;
 	private volatile Waiter eventWaiter;
-	private volatile Waiter msgWaiter;
-
-	private final File testDir = TestUtils.getTestDirectory();
-	private final SecretKey master = TestUtils.getSecretKey();
-	private final int TIMEOUT = 15000;
-	private final String SHARER = "Sharer";
-	private final String INVITEE = "Invitee";
-	private final String SHARER2 = "Sharer2";
-	private boolean respond = true;
 
-	private static final Logger LOG =
-			Logger.getLogger(ForumSharingIntegrationTest.class.getName());
-
-	private ForumSharingIntegrationTestComponent t0, t1, t2;
+	private boolean respond = true;
 
 	@Rule
-	public ExpectedException thrown=ExpectedException.none();
+	public ExpectedException thrown = ExpectedException.none();
 
 	@Before
-	public void setUp() {
-		ForumSharingIntegrationTestComponent component =
-				DaggerForumSharingIntegrationTestComponent.builder().build();
-		component.inject(this);
-		injectEagerSingletons(component);
-
-		assertTrue(testDir.mkdirs());
-		File t0Dir = new File(testDir, SHARER);
-		t0 = DaggerForumSharingIntegrationTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
-		injectEagerSingletons(t0);
-		File t1Dir = new File(testDir, INVITEE);
-		t1 = DaggerForumSharingIntegrationTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
-		injectEagerSingletons(t1);
-		File t2Dir = new File(testDir, SHARER2);
-		t2 = DaggerForumSharingIntegrationTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
-		injectEagerSingletons(t2);
-
-		identityManager0 = t0.getIdentityManager();
-		identityManager1 = t1.getIdentityManager();
-		identityManager2 = t2.getIdentityManager();
-		contactManager0 = t0.getContactManager();
-		contactManager1 = t1.getContactManager();
-		contactManager2 = t2.getContactManager();
-		forumManager0 = t0.getForumManager();
-		forumManager1 = t1.getForumManager();
-		forumSharingManager0 = t0.getForumSharingManager();
-		forumSharingManager1 = t1.getForumSharingManager();
-		forumSharingManager2 = t2.getForumSharingManager();
-		sync0 = t0.getSyncSessionFactory();
-		sync1 = t1.getSyncSessionFactory();
-		sync2 = t2.getSyncSessionFactory();
-
-		// initialize waiters fresh for each test
-		eventWaiter = new Waiter();
-		msgWaiter = new Waiter();
-	}
+	public void setUp() throws Exception {
+		super.setUp();
 
-	@Test
-	public void testSuccessfulSharing() throws Exception {
-		startLifecycles();
-		try {
-			// initialize and let invitee accept all requests
-			defaultInit(true);
+		forumManager0 = c0.getForumManager();
+		forumManager1 = c1.getForumManager();
+		forumSharingManager0 = c0.getForumSharingManager();
+		forumSharingManager1 = c1.getForumSharingManager();
+		forumSharingManager2 = c2.getForumSharingManager();
 
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, "Hi!");
+		// initialize waiter fresh for each test
+		eventWaiter = new Waiter();
 
-			// sync first request message
-			syncToInvitee();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
+		addContacts1And2();
+		addForumForSharer();
+	}
 
-			// sync response back
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.responseReceived);
+	private void addForumForSharer() throws DbException {
+		forum0 = forumManager0.addForum("Test Forum");
+	}
 
-			// forum was added successfully
-			assertEquals(0, forumSharingManager0.getInvitations().size());
-			assertEquals(1, forumManager1.getForums().size());
-
-			// invitee has one invitation message from sharer
-			List<InvitationMessage> list =
-					new ArrayList<>(forumSharingManager1
-							.getInvitationMessages(contactId0));
-			assertEquals(2, list.size());
-			// check other things are alright with the forum message
-			for (InvitationMessage m : list) {
-				if (m instanceof ForumInvitationRequest) {
-					ForumInvitationRequest invitation =
-							(ForumInvitationRequest) m;
-					assertFalse(invitation.isAvailable());
-					assertEquals(forum0.getName(), invitation.getForumName());
-					assertEquals(contactId1, invitation.getContactId());
-					assertEquals("Hi!", invitation.getMessage());
-				} else {
-					ForumInvitationResponse response =
-							(ForumInvitationResponse) m;
-					assertEquals(contactId0, response.getContactId());
-					assertTrue(response.wasAccepted());
-					assertTrue(response.isLocal());
-				}
+	@Test
+	public void testSuccessfulSharing() throws Exception {
+		// initialize and let invitee accept all requests
+		listenToEvents(true);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// sync response back
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.responseReceived);
+
+		// forum was added successfully
+		assertEquals(0, forumSharingManager0.getInvitations().size());
+		assertEquals(1, forumManager1.getForums().size());
+
+		// invitee has one invitation message from sharer
+		List<InvitationMessage> list =
+				new ArrayList<>(forumSharingManager1
+						.getInvitationMessages(contactId0From1));
+		assertEquals(2, list.size());
+		// check other things are alright with the forum message
+		for (InvitationMessage m : list) {
+			if (m instanceof ForumInvitationRequest) {
+				ForumInvitationRequest invitation =
+						(ForumInvitationRequest) m;
+				assertFalse(invitation.isAvailable());
+				assertEquals(forum0.getName(), invitation.getForumName());
+				assertEquals(contactId1From0, invitation.getContactId());
+				assertEquals("Hi!", invitation.getMessage());
+			} else {
+				ForumInvitationResponse response =
+						(ForumInvitationResponse) m;
+				assertEquals(contactId0From1, response.getContactId());
+				assertTrue(response.wasAccepted());
+				assertTrue(response.isLocal());
 			}
-			// sharer has own invitation message and response
-			assertEquals(2,
-					forumSharingManager0.getInvitationMessages(contactId1)
-							.size());
-			// forum can not be shared again
-			Contact c1 = contactManager0.getContact(contactId1);
-			assertFalse(forumSharingManager0.canBeShared(forum0.getId(), c1));
-			Contact c0 = contactManager1.getContact(contactId0);
-			assertFalse(forumSharingManager1.canBeShared(forum0.getId(), c0));
-		} finally {
-			stopLifecycles();
 		}
+		// sharer has own invitation message and response
+		assertEquals(2,
+				forumSharingManager0.getInvitationMessages(contactId1From0)
+						.size());
+		// forum can not be shared again
+		Contact c1 = contactManager0.getContact(contactId1From0);
+		assertFalse(forumSharingManager0.canBeShared(forum0.getId(), c1));
+		Contact c0 = contactManager1.getContact(contactId0From1);
+		assertFalse(forumSharingManager1.canBeShared(forum0.getId(), c0));
 	}
 
 	@Test
 	public void testDeclinedSharing() throws Exception {
-		startLifecycles();
-		try {
-			// initialize and let invitee accept all requests
-			defaultInit(false);
-
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, null);
-
-			// sync first request message
-			syncToInvitee();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
-
-			// sync response back
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.responseReceived);
-
-			// forum was not added
-			assertEquals(0, forumSharingManager0.getInvitations().size());
-			assertEquals(0, forumManager1.getForums().size());
-			// forum is no longer available to invitee who declined
-			assertEquals(0, forumSharingManager1.getInvitations().size());
-
-			// invitee has one invitation message from sharer and one response
-			List<InvitationMessage> list =
-					new ArrayList<>(forumSharingManager1
-							.getInvitationMessages(contactId0));
-			assertEquals(2, list.size());
-			// check things are alright with the forum message
-			for (InvitationMessage m : list) {
-				if (m instanceof ForumInvitationRequest) {
-					ForumInvitationRequest invitation =
-							(ForumInvitationRequest) m;
-					assertFalse(invitation.isAvailable());
-					assertEquals(forum0.getName(), invitation.getForumName());
-					assertEquals(contactId1, invitation.getContactId());
-					assertEquals(null, invitation.getMessage());
-				} else {
-					ForumInvitationResponse response =
-							(ForumInvitationResponse) m;
-					assertEquals(contactId0, response.getContactId());
-					assertFalse(response.wasAccepted());
-					assertTrue(response.isLocal());
-				}
+		// initialize and let invitee accept all requests
+		listenToEvents(false);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, null);
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// sync response back
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.responseReceived);
+
+		// forum was not added
+		assertEquals(0, forumSharingManager0.getInvitations().size());
+		assertEquals(0, forumManager1.getForums().size());
+		// forum is no longer available to invitee who declined
+		assertEquals(0, forumSharingManager1.getInvitations().size());
+
+		// invitee has one invitation message from sharer and one response
+		List<InvitationMessage> list =
+				new ArrayList<>(forumSharingManager1
+						.getInvitationMessages(contactId0From1));
+		assertEquals(2, list.size());
+		// check things are alright with the forum message
+		for (InvitationMessage m : list) {
+			if (m instanceof ForumInvitationRequest) {
+				ForumInvitationRequest invitation =
+						(ForumInvitationRequest) m;
+				assertFalse(invitation.isAvailable());
+				assertEquals(forum0.getName(), invitation.getForumName());
+				assertEquals(contactId1From0, invitation.getContactId());
+				assertEquals(null, invitation.getMessage());
+			} else {
+				ForumInvitationResponse response =
+						(ForumInvitationResponse) m;
+				assertEquals(contactId0From1, response.getContactId());
+				assertFalse(response.wasAccepted());
+				assertTrue(response.isLocal());
 			}
-			// sharer has own invitation message and response
-			assertEquals(2,
-					forumSharingManager0.getInvitationMessages(contactId1)
-							.size());
-			// forum can be shared again
-			Contact c1 = contactManager0.getContact(contactId1);
-			assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
-		} finally {
-			stopLifecycles();
 		}
+		// sharer has own invitation message and response
+		assertEquals(2,
+				forumSharingManager0.getInvitationMessages(contactId1From0)
+						.size());
+		// forum can be shared again
+		Contact c1 = contactManager0.getContact(contactId1From0);
+		assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
 	}
 
 	@Test
 	public void testInviteeLeavesAfterFinished() throws Exception {
-		startLifecycles();
-		try {
-			// initialize and let invitee accept all requests
-			defaultInit(true);
-
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, "Hi!");
-
-			// sync first request message
-			syncToInvitee();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
-
-			// sync response back
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.responseReceived);
-
-			// forum was added successfully
-			assertEquals(0, forumSharingManager0.getInvitations().size());
-			assertEquals(1, forumManager1.getForums().size());
-			assertTrue(forumManager1.getForums().contains(forum0));
-
-			// sharer shares forum with invitee
-			Contact c1 = contactManager0.getContact(contactId1);
-			assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
-					.contains(c1));
-			// invitee gets forum shared by sharer
-			Contact contact0 = contactManager1.getContact(contactId1);
-			assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
-					.contains(contact0));
-
-			// invitee un-subscribes from forum
-			forumManager1.removeForum(forum0);
-
-			// send leave message to sharer
-			syncToSharer();
-
-			// forum is gone
-			assertEquals(0, forumSharingManager0.getInvitations().size());
-			assertEquals(0, forumManager1.getForums().size());
-
-			// sharer no longer shares forum with invitee
-			assertFalse(forumSharingManager0.getSharedWith(forum0.getId())
-					.contains(c1));
-			// invitee no longer gets forum shared by sharer
-			assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
-					.contains(contact0));
-			// forum can be shared again
-			assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
-			Contact c0 = contactManager1.getContact(contactId0);
-			assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
-		} finally {
-			stopLifecycles();
-		}
+		// initialize and let invitee accept all requests
+		listenToEvents(true);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// sync response back
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.responseReceived);
+
+		// forum was added successfully
+		assertEquals(0, forumSharingManager0.getInvitations().size());
+		assertEquals(1, forumManager1.getForums().size());
+		assertTrue(forumManager1.getForums().contains(forum0));
+
+		// sharer shares forum with invitee
+		Contact c1 = contactManager0.getContact(contactId1From0);
+		assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
+				.contains(c1));
+		// invitee gets forum shared by sharer
+		Contact contact0 = contactManager1.getContact(contactId1From0);
+		assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
+				.contains(contact0));
+
+		// invitee un-subscribes from forum
+		forumManager1.removeForum(forum0);
+
+		// send leave message to sharer
+		sync1To0(1, true);
+
+		// forum is gone
+		assertEquals(0, forumSharingManager0.getInvitations().size());
+		assertEquals(0, forumManager1.getForums().size());
+
+		// sharer no longer shares forum with invitee
+		assertFalse(forumSharingManager0.getSharedWith(forum0.getId())
+				.contains(c1));
+		// invitee no longer gets forum shared by sharer
+		assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
+				.contains(contact0));
+		// forum can be shared again
+		assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
+		Contact c0 = contactManager1.getContact(contactId0From1);
+		assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
 	}
 
 	@Test
 	public void testSharerLeavesAfterFinished() throws Exception {
-		startLifecycles();
-		try {
-			// initialize and let invitee accept all requests
-			defaultInit(true);
-
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, null);
-
-			// sync first request message
-			syncToInvitee();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
-
-			// sync response back
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.responseReceived);
-
-			// forum was added successfully
-			assertEquals(0, forumSharingManager0.getInvitations().size());
-			assertEquals(1, forumManager1.getForums().size());
-			assertTrue(forumManager1.getForums().contains(forum0));
-
-			// sharer shares forum with invitee
-			Contact c1 = contactManager0.getContact(contactId1);
-			assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
-					.contains(c1));
-			// invitee gets forum shared by sharer
-			Contact contact0 = contactManager1.getContact(contactId1);
-			assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
-					.contains(contact0));
-
-			// sharer un-subscribes from forum
-			forumManager0.removeForum(forum0);
-
-			// send leave message to invitee
-			syncToInvitee();
-
-			// forum is gone for sharer, but not invitee
-			assertEquals(0, forumManager0.getForums().size());
-			assertEquals(1, forumManager1.getForums().size());
-
-			// invitee no longer shares forum with sharer
-			Contact c0 = contactManager1.getContact(contactId0);
-			assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
-					.contains(c0));
-			// sharer no longer gets forum shared by invitee
-			assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
-					.contains(contact0));
-			// forum can be shared again
-			assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
-		} finally {
-			stopLifecycles();
-		}
+		// initialize and let invitee accept all requests
+		listenToEvents(true);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, null);
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// sync response back
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.responseReceived);
+
+		// forum was added successfully
+		assertEquals(0, forumSharingManager0.getInvitations().size());
+		assertEquals(1, forumManager1.getForums().size());
+		assertTrue(forumManager1.getForums().contains(forum0));
+
+		// sharer shares forum with invitee
+		Contact c1 = contactManager0.getContact(contactId1From0);
+		assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
+				.contains(c1));
+		// invitee gets forum shared by sharer
+		Contact contact0 = contactManager1.getContact(contactId1From0);
+		assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
+				.contains(contact0));
+
+		// sharer un-subscribes from forum
+		forumManager0.removeForum(forum0);
+
+		// send leave message to invitee
+		sync0To1(1, true);
+
+		// forum is gone for sharer, but not invitee
+		assertEquals(0, forumManager0.getForums().size());
+		assertEquals(1, forumManager1.getForums().size());
+
+		// invitee no longer shares forum with sharer
+		Contact c0 = contactManager1.getContact(contactId0From1);
+		assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
+				.contains(c0));
+		// sharer no longer gets forum shared by invitee
+		assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
+				.contains(contact0));
+		// forum can be shared again
+		assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
 	}
 
 	@Test
 	public void testSharerLeavesBeforeResponse() throws Exception {
-		startLifecycles();
-		try {
-			// initialize except event listeners
-			defaultInit(true);
+		// initialize except event listeners
+		listenToEvents(true);
 
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, null);
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, null);
 
-			// sharer un-subscribes from forum
-			forumManager0.removeForum(forum0);
+		// sharer un-subscribes from forum
+		forumManager0.removeForum(forum0);
 
-			// prevent invitee response before syncing messages
-			respond = false;
+		// prevent invitee response before syncing messages
+		respond = false;
 
-			// sync first request message and leave message
-			deliverMessage(sync0, contactId0, sync1, contactId1, 2,
-					"Sharer to Invitee");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
+		// sync first request message and leave message
+		sync0To1(2, true);
+
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
 
-			// ensure that invitee has no forum invitations available
-			assertEquals(0, forumSharingManager1.getInvitations().size());
-			assertEquals(0, forumManager1.getForums().size());
+		// ensure that invitee has no forum invitations available
+		assertEquals(0, forumSharingManager1.getInvitations().size());
+		assertEquals(0, forumManager1.getForums().size());
 
-			// Try again, this time allow the response
-			addForumForSharer();
-			respond = true;
+		// Try again, this time allow the response
+		addForumForSharer();
+		respond = true;
 
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, null);
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, null);
 
-			// sharer un-subscribes from forum
-			forumManager0.removeForum(forum0);
+		// sharer un-subscribes from forum
+		forumManager0.removeForum(forum0);
 
-			// sync first request message and leave message
-			deliverMessage(sync0, contactId0, sync1, contactId1, 2,
-					"Sharer to Invitee");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
+		// sync first request message and leave message
+		sync0To1(2, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
 
-			// ensure that invitee has no forum invitations available
-			assertEquals(0, forumSharingManager1.getInvitations().size());
-			assertEquals(1, forumManager1.getForums().size());
-		} finally {
-			stopLifecycles();
-		}
+		// ensure that invitee has no forum invitations available
+		assertEquals(0, forumSharingManager1.getInvitations().size());
+		assertEquals(1, forumManager1.getForums().size());
 	}
 
 	@Test
 	public void testSessionIdReuse() throws Exception {
-		startLifecycles();
-		try {
-			// initialize and let invitee accept all requests
-			defaultInit(true);
-
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, "Hi!");
-
-			// sync first request message
-			syncToInvitee();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
-
-			// sync response back
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.responseReceived);
-
-			// forum was added successfully
-			assertEquals(1, forumManager1.getForums().size());
-
-			// reset event received state
-			listener1.requestReceived = false;
-
-			// get SessionId from invitation
-			List<InvitationMessage> list = new ArrayList<>(
-					forumSharingManager1
-							.getInvitationMessages(contactId0));
-			assertEquals(2, list.size());
-			InvitationMessage msg = list.get(0);
-			SessionId sessionId = msg.getSessionId();
-			assertEquals(sessionId, list.get(1).getSessionId());
-
-			// get all sorts of stuff needed to send a message
-			DatabaseComponent db = t0.getDatabaseComponent();
-			MessageQueueManager queue = t0.getMessageQueueManager();
-			Contact c1 = contactManager0.getContact(contactId1);
-			ContactGroupFactory groupFactory = t0.getContactGroupFactory();
-			Group group = groupFactory.createContactGroup(CLIENT_ID, c1);
-			long time = clock.currentTimeMillis();
-			BdfList bodyList = BdfList.of(SHARE_MSG_TYPE_INVITATION,
-					sessionId.getBytes(),
-					TestUtils.getRandomString(42),
-					TestUtils.getRandomBytes(FORUM_SALT_LENGTH)
-			);
-			byte[] body = t0.getClientHelper().toByteArray(bodyList);
-
-			// add the message to the queue
-			Transaction txn = db.startTransaction(false);
-			try {
-				queue.sendMessage(txn, group, time, body, new Metadata());
-				db.commitTransaction(txn);
-			} finally {
-				db.endTransaction(txn);
-			}
+		// initialize and let invitee accept all requests
+		listenToEvents(true);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// sync response back
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.responseReceived);
+
+		// forum was added successfully
+		assertEquals(1, forumManager1.getForums().size());
+
+		// reset event received state
+		listener1.requestReceived = false;
+
+		// get SessionId from invitation
+		List<InvitationMessage> list = new ArrayList<>(
+				forumSharingManager1
+						.getInvitationMessages(contactId0From1));
+		assertEquals(2, list.size());
+		InvitationMessage msg = list.get(0);
+		SessionId sessionId = msg.getSessionId();
+		assertEquals(sessionId, list.get(1).getSessionId());
+
+		// get all sorts of stuff needed to send a message
+		MessageQueueManager queue = c0.getMessageQueueManager();
+		Contact c1 = contactManager0.getContact(contactId1From0);
+		Group group = contactGroupFactory.createContactGroup(CLIENT_ID, c1);
+		long time = clock.currentTimeMillis();
+		BdfList bodyList = BdfList.of(SHARE_MSG_TYPE_INVITATION,
+				sessionId.getBytes(),
+				TestUtils.getRandomString(42),
+				TestUtils.getRandomBytes(FORUM_SALT_LENGTH)
+		);
+		byte[] body = clientHelper.toByteArray(bodyList);
 
-			// actually send the message
-			syncToInvitee();
-			// make sure there was no new request received
-			assertFalse(listener1.requestReceived);
+		// add the message to the queue
+		Transaction txn = db0.startTransaction(false);
+		try {
+			queue.sendMessage(txn, group, time, body, new Metadata());
+			db0.commitTransaction(txn);
 		} finally {
-			stopLifecycles();
+			db0.endTransaction(txn);
 		}
+
+		// actually send the message
+		sync0To1(1, false);
+		// make sure there was no new request received
+		assertFalse(listener1.requestReceived);
 	}
 
 	@Test
 	public void testSharingSameForumWithEachOther() throws Exception {
-		startLifecycles();
-		try {
-			// initialize and let invitee accept all requests
-			defaultInit(true);
-
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, "Hi!");
+		// initialize and let invitee accept all requests
+		listenToEvents(true);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// sync response back
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.responseReceived);
+
+		// forum was added successfully
+		assertEquals(1, forumManager1.getForums().size());
+		assertEquals(2,
+				forumSharingManager0.getInvitationMessages(contactId1From0)
+						.size());
+
+		// invitee now shares same forum back
+		forumSharingManager1.sendInvitation(forum0.getId(),
+				contactId0From1,
+				"I am re-sharing this forum with you.");
+
+		// sync re-share invitation
+		sync1To0(1, false);
+
+		// make sure that no new request was received
+		assertFalse(listener0.requestReceived);
+		assertEquals(2,
+				forumSharingManager0.getInvitationMessages(contactId1From0)
+						.size());
+		assertEquals(0, forumSharingManager0.getInvitations().size());
+	}
 
-			// sync first request message
-			syncToInvitee();
+	@Test
+	public void testSharingSameForumWithEachOtherAtSameTime() throws Exception {
+		// initialize and let invitee accept all requests
+		listenToEvents(true);
+
+		// invitee adds the same forum
+		Transaction txn = db1.startTransaction(false);
+		db1.addGroup(txn, forum0.getGroup());
+		db1.commitTransaction(txn);
+		db1.endTransaction(txn);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
+
+		// invitee now shares same forum back
+		forumSharingManager1.sendInvitation(forum0.getId(),
+				contactId0From1, "I am re-sharing this forum with you.");
+
+		// find out who should be Alice, because of random keys
+		Bytes key0 = new Bytes(author0.getPublicKey());
+		Bytes key1 = new Bytes(author1.getPublicKey());
+
+		// only now sync first request message
+		boolean alice = key1.compareTo(key0) < 0;
+		if (alice) {
+			sync0To1(1, false);
+			assertFalse(listener1.requestReceived);
+		} else {
+			sync0To1(1, true);
 			eventWaiter.await(TIMEOUT, 1);
 			assertTrue(listener1.requestReceived);
+		}
 
-			// sync response back
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.responseReceived);
-
-			// forum was added successfully
-			assertEquals(1, forumManager1.getForums().size());
-			assertEquals(2,
-					forumSharingManager0.getInvitationMessages(contactId1)
-							.size());
-
-			// invitee now shares same forum back
-			forumSharingManager1.sendInvitation(forum0.getId(),
-					contactId0,
-					"I am re-sharing this forum with you.");
-
-			// sync re-share invitation
-			syncToSharer();
-
-			// make sure that no new request was received
+		// sync second invitation
+		alice = key0.compareTo(key1) < 0;
+		if (alice) {
+			sync1To0(1, false);
 			assertFalse(listener0.requestReceived);
-			assertEquals(2,
-					forumSharingManager0.getInvitationMessages(contactId1)
-							.size());
-			assertEquals(0, forumSharingManager0.getInvitations().size());
-		} finally {
-			stopLifecycles();
-		}
-	}
 
-	@Test
-	public void testSharingSameForumWithEachOtherAtSameTime() throws Exception {
-		startLifecycles();
-		try {
-			// initialize and let invitee accept all requests
-			defaultInit(true);
-
-			// invitee adds the same forum
-			DatabaseComponent db1 = t1.getDatabaseComponent();
-			Transaction txn = db1.startTransaction(false);
-			db1.addGroup(txn, forum0.getGroup());
-			db1.commitTransaction(txn);
-			db1.endTransaction(txn);
-
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, "Hi!");
-
-			// invitee now shares same forum back
-			forumSharingManager1.sendInvitation(forum0.getId(),
-					contactId0, "I am re-sharing this forum with you.");
-
-			// find out who should be Alice, because of random keys
-			Bytes key0 = new Bytes(author0.getPublicKey());
-			Bytes key1 = new Bytes(author1.getPublicKey());
-
-			// only now sync first request message
-			boolean alice = key1.compareTo(key0) < 0;
-			syncToInvitee();
-			if (alice) {
-				assertFalse(listener1.requestReceived);
-			} else {
-				eventWaiter.await(TIMEOUT, 1);
-				assertTrue(listener1.requestReceived);
-			}
+			// sharer did not receive request, but response to own request
+			eventWaiter.await(TIMEOUT, 1);
+			assertTrue(listener0.responseReceived);
 
-			// sync second invitation
-			alice = key0.compareTo(key1) < 0;
-			syncToSharer();
-			if (alice) {
-				assertFalse(listener0.requestReceived);
+			assertEquals(2, forumSharingManager0
+					.getInvitationMessages(contactId1From0).size());
+			assertEquals(3, forumSharingManager1
+					.getInvitationMessages(contactId0From1).size());
+		} else {
+			sync1To0(1, true);
+			eventWaiter.await(TIMEOUT, 1);
+			assertTrue(listener0.requestReceived);
 
-				// sharer did not receive request, but response to own request
-				eventWaiter.await(TIMEOUT, 1);
-				assertTrue(listener0.responseReceived);
+			// send response from sharer to invitee and make sure it arrived
+			sync0To1(1, true);
+			eventWaiter.await(TIMEOUT, 1);
+			assertTrue(listener1.responseReceived);
 
-				assertEquals(2, forumSharingManager0
-						.getInvitationMessages(contactId1).size());
-				assertEquals(3, forumSharingManager1
-						.getInvitationMessages(contactId0).size());
-			} else {
-				eventWaiter.await(TIMEOUT, 1);
-				assertTrue(listener0.requestReceived);
-
-				// send response from sharer to invitee and make sure it arrived
-				syncToInvitee();
-				eventWaiter.await(TIMEOUT, 1);
-				assertTrue(listener1.responseReceived);
-
-				assertEquals(3, forumSharingManager0
-						.getInvitationMessages(contactId1).size());
-				assertEquals(2, forumSharingManager1
-						.getInvitationMessages(contactId0).size());
-			}
-		} finally {
-			stopLifecycles();
+			assertEquals(3, forumSharingManager0
+					.getInvitationMessages(contactId1From0).size());
+			assertEquals(2, forumSharingManager1
+					.getInvitationMessages(contactId0From1).size());
 		}
 	}
 
 	@Test
 	public void testContactRemoved() throws Exception {
-		startLifecycles();
-		try {
-			// initialize and let invitee accept all requests
-			defaultInit(true);
-
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, "Hi!");
-
-			// sync first request message
-			syncToInvitee();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
-
-			// sync response back
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.responseReceived);
-
-			// forum was added successfully
-			assertEquals(1, forumManager1.getForums().size());
-			assertEquals(1,
-					forumSharingManager0.getSharedWith(forum0.getId()).size());
-
-			// remember SessionId from invitation
-			List<InvitationMessage> list = new ArrayList<>(
-					forumSharingManager1
-							.getInvitationMessages(contactId0));
-			assertEquals(2, list.size());
-			InvitationMessage msg = list.get(0);
-			SessionId sessionId = msg.getSessionId();
-			assertEquals(sessionId, list.get(1).getSessionId());
-
-			// contacts now remove each other
-			contactManager0.removeContact(contactId1);
-			contactManager2.removeContact(contactId21);
-			contactManager1.removeContact(contactId0);
-			contactManager1.removeContact(contactId2);
-
-			// make sure sharer does share the forum with nobody now
-			assertEquals(0,
-					forumSharingManager0.getSharedWith(forum0.getId()).size());
-
-			// contacts add each other again
-			addDefaultContacts();
-
-			// get all sorts of stuff needed to send a message
-			DatabaseComponent db = t0.getDatabaseComponent();
-			MessageQueueManager queue = t0.getMessageQueueManager();
-			Contact c1 = contactManager0.getContact(contactId1);
-			ContactGroupFactory groupFactory = t0.getContactGroupFactory();
-			Group group = groupFactory.createContactGroup(CLIENT_ID, c1);
-			long time = clock.currentTimeMillis();
-
-			// construct a new message re-using the old SessionId
-			BdfList bodyList = BdfList.of(SHARE_MSG_TYPE_INVITATION,
-					sessionId.getBytes(),
-					TestUtils.getRandomString(42),
-					TestUtils.getRandomBytes(FORUM_SALT_LENGTH)
-			);
-			byte[] body = t0.getClientHelper().toByteArray(bodyList);
-
-			// add the message to the queue
-			Transaction txn = db.startTransaction(false);
-			try {
-				queue.sendMessage(txn, group, time, body, new Metadata());
-				db.commitTransaction(txn);
-			} finally {
-				db.endTransaction(txn);
-			}
+		// initialize and let invitee accept all requests
+		listenToEvents(true);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// sync response back
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.responseReceived);
+
+		// forum was added successfully
+		assertEquals(1, forumManager1.getForums().size());
+		assertEquals(1,
+				forumSharingManager0.getSharedWith(forum0.getId()).size());
+
+		// remember SessionId from invitation
+		List<InvitationMessage> list = new ArrayList<>(
+				forumSharingManager1
+						.getInvitationMessages(contactId0From1));
+		assertEquals(2, list.size());
+		InvitationMessage msg = list.get(0);
+		SessionId sessionId = msg.getSessionId();
+		assertEquals(sessionId, list.get(1).getSessionId());
+
+		// contacts now remove each other
+		removeAllContacts();
+
+		// make sure sharer does share the forum with nobody now
+		assertEquals(0,
+				forumSharingManager0.getSharedWith(forum0.getId()).size());
+
+		// contacts add each other again
+		addDefaultContacts();
+		addContacts1And2();
+
+		// get all sorts of stuff needed to send a message
+		MessageQueueManager queue = c0.getMessageQueueManager();
+		Contact c1 = contactManager0.getContact(contactId1From0);
+		Group group = contactGroupFactory.createContactGroup(CLIENT_ID, c1);
+		long time = clock.currentTimeMillis();
+
+		// construct a new message re-using the old SessionId
+		BdfList bodyList = BdfList.of(SHARE_MSG_TYPE_INVITATION,
+				sessionId.getBytes(),
+				TestUtils.getRandomString(42),
+				TestUtils.getRandomBytes(FORUM_SALT_LENGTH)
+		);
+		byte[] body = clientHelper.toByteArray(bodyList);
 
-			// actually send the message
-			syncToInvitee();
-			eventWaiter.await(TIMEOUT, 1);
-			// make sure the new request was received with the same sessionId
-			// as proof that the state got deleted along with contacts
-			assertTrue(listener1.requestReceived);
+		// add the message to the queue
+		Transaction txn = db0.startTransaction(false);
+		try {
+			queue.sendMessage(txn, group, time, body, new Metadata());
+			db0.commitTransaction(txn);
 		} finally {
-			stopLifecycles();
+			db0.endTransaction(txn);
 		}
+
+		// actually send the message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		// make sure the new request was received with the same sessionId
+		// as proof that the state got deleted along with contacts
+		assertTrue(listener1.requestReceived);
 	}
 
 	@Test
 	public void testTwoContactsShareSameForum() throws Exception {
-		startLifecycles();
-		try {
-			// initialize
-			getDefaultIdentities();
-			addDefaultContacts();
-			addForumForSharer();
-
-			// second sharer adds the same forum
-			DatabaseComponent db2 = t2.getDatabaseComponent();
-			Transaction txn = db2.startTransaction(false);
-			db2.addGroup(txn, forum0.getGroup());
-			db2.commitTransaction(txn);
-			db2.endTransaction(txn);
-
-			// add listeners
-			listener0 = new SharerListener();
-			t0.getEventBus().addListener(listener0);
-			listener1 = new InviteeListener(true, false);
-			t1.getEventBus().addListener(listener1);
-			listener2 = new SharerListener();
-			t2.getEventBus().addListener(listener2);
-
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, "Hi!");
-			// sync first request message
-			syncToInvitee();
-
-			// second sharer sends invitation for same forum
-			forumSharingManager2
-					.sendInvitation(forum0.getId(), contactId1, null);
-			// sync second request message
-			deliverMessage(sync2, contactId2, sync1, contactId1, 1,
-					"Sharer2 to Invitee");
-
-			// make sure we now have two invitations to the same forum available
-			Collection<SharingInvitationItem> forums =
-					forumSharingManager1.getInvitations();
-			assertEquals(1, forums.size());
-			assertEquals(2, forums.iterator().next().getNewSharers().size());
-			assertEquals(forum0, forums.iterator().next().getShareable());
-			assertEquals(2,
-					forumSharingManager1.getSharedBy(forum0.getId()).size());
-
-			// make sure both sharers actually share the forum
-			Collection<Contact> contacts =
-					forumSharingManager1.getSharedBy(forum0.getId());
-			assertEquals(2, contacts.size());
-
-			// answer second request
-			Contact c2 = contactManager1.getContact(contactId2);
-			forumSharingManager1.respondToInvitation(forum0, c2, true);
-			// sync response
-			deliverMessage(sync1, contactId21, sync2, contactId2, 1,
-					"Invitee to Sharer2");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener2.responseReceived);
-
-			// answer first request
-			Contact c0 =
-					contactManager1.getContact(contactId0);
-			forumSharingManager1.respondToInvitation(forum0, c0, true);
-			// sync response
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.responseReceived);
-		} finally {
-			stopLifecycles();
-		}
+		// second sharer adds the same forum
+		Transaction txn = db2.startTransaction(false);
+		db2.addGroup(txn, forum0.getGroup());
+		db2.commitTransaction(txn);
+		db2.endTransaction(txn);
+
+		// add listeners
+		listener0 = new SharerListener();
+		c0.getEventBus().addListener(listener0);
+		listener1 = new InviteeListener(true, false);
+		c1.getEventBus().addListener(listener1);
+		listener2 = new SharerListener();
+		c2.getEventBus().addListener(listener2);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
+		// sync first request message
+		sync0To1(1, true);
+
+		// second sharer sends invitation for same forum
+		assertTrue(contactId1From2 != null);
+		forumSharingManager2
+				.sendInvitation(forum0.getId(), contactId1From2, null);
+		// sync second request message
+		sync2To1(1, true);
+
+		// make sure we now have two invitations to the same forum available
+		Collection<SharingInvitationItem> forums =
+				forumSharingManager1.getInvitations();
+		assertEquals(1, forums.size());
+		assertEquals(2, forums.iterator().next().getNewSharers().size());
+		assertEquals(forum0, forums.iterator().next().getShareable());
+		assertEquals(2,
+				forumSharingManager1.getSharedBy(forum0.getId()).size());
+
+		// make sure both sharers actually share the forum
+		Collection<Contact> contacts =
+				forumSharingManager1.getSharedBy(forum0.getId());
+		assertEquals(2, contacts.size());
+
+		// answer second request
+		Contact contact2From1 = contactManager1.getContact(contactId2From1);
+		forumSharingManager1.respondToInvitation(forum0, contact2From1, true);
+		// sync response
+		sync1To2(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener2.responseReceived);
+
+		// answer first request
+		Contact c0 =
+				contactManager1.getContact(contactId0From1);
+		forumSharingManager1.respondToInvitation(forum0, c0, true);
+		// sync response
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.responseReceived);
 	}
 
 	@Test
 	public void testSyncAfterReSharing() throws Exception {
-		startLifecycles();
-		try {
-			// initialize and let invitee accept all requests
-			defaultInit(true);
-
-			// send invitation
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, "Hi!");
-
-			// sync first request message
-			syncToInvitee();
-			eventWaiter.await(TIMEOUT, 1);
-
-			// sync response back
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
-
-			// sharer posts into the forum
-			long time = clock.currentTimeMillis();
-			String body = TestUtils.getRandomString(42);
-			ForumPost p = forumPostFactory
-					.createPost(forum0.getId(), time, null, author0,
-							body);
-			forumManager0.addLocalPost(p);
-
-			// sync forum post
-			syncToInvitee();
-
-			// make sure forum post arrived
-			Collection<ForumPostHeader> headers =
-					forumManager1.getPostHeaders(forum0.getId());
-			assertEquals(1, headers.size());
-			ForumPostHeader header = headers.iterator().next();
-			assertEquals(p.getMessage().getId(), header.getId());
-			assertEquals(author0, header.getAuthor());
-
-			// now invitee creates a post
-			time = clock.currentTimeMillis();
-			body = TestUtils.getRandomString(42);
-			p = forumPostFactory
-					.createPost(forum0.getId(), time, null, author1,
-							body);
-			forumManager1.addLocalPost(p);
-
-			// sync forum post
-			syncToSharer();
-
-			// make sure forum post arrived
-			headers = forumManager1.getPostHeaders(forum0.getId());
-			assertEquals(2, headers.size());
-			boolean found = false;
-			for (ForumPostHeader h : headers) {
-				if (p.getMessage().getId().equals(h.getId())) {
-					found = true;
-					assertEquals(author1, h.getAuthor());
-				}
+		// initialize and let invitee accept all requests
+		listenToEvents(true);
+
+		// send invitation
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+
+		// sync response back
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+
+		// sharer posts into the forum
+		long time = clock.currentTimeMillis();
+		String body = TestUtils.getRandomString(42);
+		ForumPost p = forumPostFactory
+				.createPost(forum0.getId(), time, null, author0,
+						body);
+		forumManager0.addLocalPost(p);
+
+		// sync forum post
+		sync0To1(1, true);
+
+		// make sure forum post arrived
+		Collection<ForumPostHeader> headers =
+				forumManager1.getPostHeaders(forum0.getId());
+		assertEquals(1, headers.size());
+		ForumPostHeader header = headers.iterator().next();
+		assertEquals(p.getMessage().getId(), header.getId());
+		assertEquals(author0, header.getAuthor());
+
+		// now invitee creates a post
+		time = clock.currentTimeMillis();
+		body = TestUtils.getRandomString(42);
+		p = forumPostFactory
+				.createPost(forum0.getId(), time, null, author1,
+						body);
+		forumManager1.addLocalPost(p);
+
+		// sync forum post
+		sync1To0(1, true);
+
+		// make sure forum post arrived
+		headers = forumManager1.getPostHeaders(forum0.getId());
+		assertEquals(2, headers.size());
+		boolean found = false;
+		for (ForumPostHeader h : headers) {
+			if (p.getMessage().getId().equals(h.getId())) {
+				found = true;
+				assertEquals(author1, h.getAuthor());
 			}
-			assertTrue(found);
-
-			// contacts remove each other
-			contactManager0.removeContact(contactId1);
-			contactManager1.removeContact(contactId0);
-			contactManager1.removeContact(contactId2);
-			contactManager2.removeContact(contactId21);
-
-			// contacts add each other back
-			addDefaultContacts();
-
-			// send invitation again
-			forumSharingManager0
-					.sendInvitation(forum0.getId(), contactId1, "Hi!");
-
-			// sync first request message
-			syncToInvitee();
-			eventWaiter.await(TIMEOUT, 1);
+		}
+		assertTrue(found);
 
-			// sync response back
-			syncToSharer();
-			eventWaiter.await(TIMEOUT, 1);
+		// contacts remove each other
+		removeAllContacts();
 
-			// now invitee creates a post
-			time = clock.currentTimeMillis();
-			body = TestUtils.getRandomString(42);
-			p = forumPostFactory
-					.createPost(forum0.getId(), time, null, author1,
-							body);
-			forumManager1.addLocalPost(p);
-
-			// sync forum post
-			syncToSharer();
-
-			// make sure forum post arrived
-			headers = forumManager1.getPostHeaders(forum0.getId());
-			assertEquals(3, headers.size());
-			found = false;
-			for (ForumPostHeader h : headers) {
-				if (p.getMessage().getId().equals(h.getId())) {
-					found = true;
-					assertEquals(author1, h.getAuthor());
-				}
+		// contacts add each other back
+		addDefaultContacts();
+		addContacts1And2();
+
+		// send invitation again
+		forumSharingManager0
+				.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+
+		// sync response back
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+
+		// now invitee creates a post
+		time = clock.currentTimeMillis();
+		body = TestUtils.getRandomString(42);
+		p = forumPostFactory
+				.createPost(forum0.getId(), time, null, author1,
+						body);
+		forumManager1.addLocalPost(p);
+
+		// sync forum post
+		sync1To0(1, true);
+
+		// make sure forum post arrived
+		headers = forumManager1.getPostHeaders(forum0.getId());
+		assertEquals(3, headers.size());
+		found = false;
+		for (ForumPostHeader h : headers) {
+			if (p.getMessage().getId().equals(h.getId())) {
+				found = true;
+				assertEquals(author1, h.getAuthor());
 			}
-			assertTrue(found);
-		} finally {
-			stopLifecycles();
 		}
-	}
-
-
-	@After
-	public void tearDown() throws InterruptedException {
-		TestUtils.deleteTestDirectory(testDir);
+		assertTrue(found);
 	}
 
 	private class SharerListener implements EventListener {
@@ -917,17 +758,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 		@Override
 		public void eventOccurred(Event e) {
-			if (e instanceof MessageStateChangedEvent) {
-				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
-				State s = event.getState();
-				if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
-					LOG.info("TEST: Sharer received message");
-					msgWaiter.resume();
-				}
-			} else if (e instanceof ForumInvitationResponseReceivedEvent) {
-				ForumInvitationResponseReceivedEvent event =
-						(ForumInvitationResponseReceivedEvent) e;
-				eventWaiter.assertEquals(contactId1, event.getContactId());
+			if (e instanceof ForumInvitationResponseReceivedEvent) {
 				responseReceived = true;
 				eventWaiter.resume();
 			}
@@ -935,11 +766,11 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			else if (e instanceof ForumInvitationReceivedEvent) {
 				ForumInvitationReceivedEvent event =
 						(ForumInvitationReceivedEvent) e;
-				eventWaiter.assertEquals(contactId1, event.getContactId());
+				eventWaiter.assertEquals(contactId1From0, event.getContactId());
 				requestReceived = true;
 				Forum f = event.getShareable();
 				try {
-					Contact c = contactManager0.getContact(contactId1);
+					Contact c = contactManager0.getContact(contactId1From0);
 					forumSharingManager0.respondToInvitation(f, c, true);
 				} catch (DbException ex) {
 					eventWaiter.rethrow(ex);
@@ -968,14 +799,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 
 		@Override
 		public void eventOccurred(Event e) {
-			if (e instanceof MessageStateChangedEvent) {
-				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
-				State s = event.getState();
-				if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
-					LOG.info("TEST: Invitee received message");
-					msgWaiter.resume();
-				}
-			} else if (e instanceof ForumInvitationReceivedEvent) {
+			if (e instanceof ForumInvitationReceivedEvent) {
 				ForumInvitationReceivedEvent event =
 						(ForumInvitationReceivedEvent) e;
 				requestReceived = true;
@@ -988,11 +812,12 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 							forumSharingManager1.getInvitations().iterator()
 									.next();
 					eventWaiter.assertEquals(f, invitation.getShareable());
-				if (respond) {
-					Contact c =
-							contactManager1.getContact(event.getContactId());
-					forumSharingManager1.respondToInvitation(f, c, accept);
-				}
+					if (respond) {
+						Contact c =
+								contactManager1
+										.getContact(event.getContactId());
+						forumSharingManager1.respondToInvitation(f, c, accept);
+					}
 				} catch (DbException ex) {
 					eventWaiter.rethrow(ex);
 				} finally {
@@ -1003,131 +828,20 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
 			else if (e instanceof ForumInvitationResponseReceivedEvent) {
 				ForumInvitationResponseReceivedEvent event =
 						(ForumInvitationResponseReceivedEvent) e;
-				eventWaiter.assertEquals(contactId0, event.getContactId());
+				eventWaiter.assertEquals(contactId0From1, event.getContactId());
 				responseReceived = true;
 				eventWaiter.resume();
 			}
 		}
 	}
 
-	private void startLifecycles() throws InterruptedException {
-		// Start the lifecycle manager and wait for it to finish
-		lifecycleManager0 = t0.getLifecycleManager();
-		lifecycleManager1 = t1.getLifecycleManager();
-		lifecycleManager2 = t2.getLifecycleManager();
-		lifecycleManager0.startServices(SHARER);
-		lifecycleManager1.startServices(INVITEE);
-		lifecycleManager2.startServices(SHARER2);
-		lifecycleManager0.waitForStartup();
-		lifecycleManager1.waitForStartup();
-		lifecycleManager2.waitForStartup();
-	}
-
-	private void stopLifecycles() throws InterruptedException {
-		// Clean up
-		lifecycleManager0.stopServices();
-		lifecycleManager1.stopServices();
-		lifecycleManager2.stopServices();
-		lifecycleManager0.waitForShutdown();
-		lifecycleManager1.waitForShutdown();
-		lifecycleManager2.waitForShutdown();
-	}
-
-	private void defaultInit(boolean accept) throws DbException {
-		getDefaultIdentities();
-		addDefaultContacts();
-		addForumForSharer();
-		listenToEvents(accept);
-	}
-
-	private void getDefaultIdentities() throws DbException {
-		author0 = identityManager0.getLocalAuthor();
-		author1 = identityManager1.getLocalAuthor();
-		author2 = identityManager2.getLocalAuthor();
-	}
-
-	private void addDefaultContacts() throws DbException {
-		// sharer adds invitee as contact
-		contactId1 = contactManager0.addContact(author1,
-				author0.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		// second sharer does the same
-		contactId21 = contactManager2.addContact(author1,
-				author2.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		// invitee adds sharers back
-		contactId0 = contactManager1.addContact(author0,
-				author1.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		contactId2 = contactManager1.addContact(author2,
-				author1.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-	}
-
-	private void addForumForSharer() throws DbException {
-		// sharer creates forum
-		forum0 = forumManager0.addForum("Test Forum");
-	}
-
-	private void listenToEvents(boolean accept) {
+	private void listenToEvents(boolean accept) throws DbException {
 		listener0 = new SharerListener();
-		t0.getEventBus().addListener(listener0);
+		c0.getEventBus().addListener(listener0);
 		listener1 = new InviteeListener(accept);
-		t1.getEventBus().addListener(listener1);
+		c1.getEventBus().addListener(listener1);
 		listener2 = new SharerListener();
-		t2.getEventBus().addListener(listener2);
-	}
-
-	private void syncToInvitee() throws IOException, TimeoutException {
-		deliverMessage(sync0, contactId0, sync1, contactId1, 1,
-				"Sharer to Invitee");
-	}
-
-	private void syncToSharer() throws IOException, TimeoutException {
-		deliverMessage(sync1, contactId1, sync0, contactId0, 1,
-				"Invitee to Sharer");
-	}
-
-	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
-			SyncSessionFactory toSync, ContactId toId, int num, String debug)
-			throws IOException, TimeoutException {
-
-		if (debug != null) LOG.info("TEST: Sending message from " + debug);
-
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		// Create an outgoing sync session
-		SyncSession sessionFrom =
-				fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
-		// Write whatever needs to be written
-		sessionFrom.run();
-		out.close();
-
-		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-		// Create an incoming sync session
-		SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
-		// Read whatever needs to be read
-		sessionTo.run();
-		in.close();
-
-		// wait for message to actually arrive
-		msgWaiter.await(TIMEOUT, num);
-	}
-
-	private void injectEagerSingletons(
-			ForumSharingIntegrationTestComponent component) {
-
-		component.inject(new LifecycleModule.EagerSingletons());
-		component.inject(new ForumModule.EagerSingletons());
-		component.inject(new CryptoModule.EagerSingletons());
-		component.inject(new ContactModule.EagerSingletons());
-		component.inject(new TransportModule.EagerSingletons());
-		component.inject(new SharingModule.EagerSingletons());
-		component.inject(new SyncModule.EagerSingletons());
-		component.inject(new PropertiesModule.EagerSingletons());
+		c2.getEventBus().addListener(listener2);
 	}
 
 }
diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java
index ab8fa52cc1bbc23b0ff5a70d52bdf6e3723c6195..4e1dbc0cd25b1296f1951b2480657f10add853ed 100644
--- a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java
@@ -1,65 +1,22 @@
 package org.briarproject;
 
-import net.jodah.concurrentunit.Waiter;
-
-import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.clients.ContactGroupFactory;
 import org.briarproject.api.clients.MessageTracker.GroupCount;
 import org.briarproject.api.contact.Contact;
-import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.crypto.KeyPair;
-import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.data.BdfList;
-import org.briarproject.api.db.DatabaseComponent;
-import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Transaction;
-import org.briarproject.api.event.Event;
-import org.briarproject.api.event.EventListener;
-import org.briarproject.api.event.MessageStateChangedEvent;
-import org.briarproject.api.identity.AuthorFactory;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.identity.LocalAuthor;
-import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.privategroup.GroupMember;
 import org.briarproject.api.privategroup.GroupMessage;
-import org.briarproject.api.privategroup.GroupMessageFactory;
 import org.briarproject.api.privategroup.GroupMessageHeader;
 import org.briarproject.api.privategroup.JoinMessageHeader;
 import org.briarproject.api.privategroup.PrivateGroup;
-import org.briarproject.api.privategroup.PrivateGroupFactory;
 import org.briarproject.api.privategroup.PrivateGroupManager;
-import org.briarproject.api.privategroup.invitation.GroupInvitationFactory;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
-import org.briarproject.api.sync.SyncSession;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.api.system.Clock;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.privategroup.PrivateGroupModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.transport.TransportModule;
-import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
 import java.util.Collection;
-import java.util.concurrent.TimeoutException;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
 
-import static org.briarproject.TestPluginsModule.MAX_LATENCY;
 import static org.briarproject.TestUtils.getRandomBytes;
 import static org.briarproject.api.identity.Author.Status.VERIFIED;
 import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
@@ -68,110 +25,32 @@ import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_US;
 import static org.briarproject.api.privategroup.Visibility.VISIBLE;
 import static org.briarproject.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE;
 import static org.briarproject.api.sync.Group.Visibility.SHARED;
-import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
-import static org.briarproject.api.sync.ValidationManager.State.INVALID;
-import static org.briarproject.api.sync.ValidationManager.State.PENDING;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
-	private LifecycleManager lifecycleManager0, lifecycleManager1,
-			lifecycleManager2;
-	private SyncSessionFactory sync0, sync1, sync2;
-	private PrivateGroupManager groupManager0, groupManager1, groupManager2;
-	private ContactManager contactManager0, contactManager1, contactManager2;
-	private ContactId contactId01, contactId02, contactId1, contactId2;
-	private IdentityManager identityManager0, identityManager1,
-			identityManager2;
-	private LocalAuthor author0, author1, author2;
-	private DatabaseComponent db0, db1, db2;
 	private PrivateGroup privateGroup0;
 	private GroupId groupId0;
-
-	@Inject
-	Clock clock;
-	@Inject
-	AuthorFactory authorFactory;
-	@Inject
-	ClientHelper clientHelper;
-	@Inject
-	CryptoComponent crypto;
-	@Inject
-	ContactGroupFactory contactGroupFactory;
-	@Inject
-	PrivateGroupFactory privateGroupFactory;
-	@Inject
-	GroupMessageFactory groupMessageFactory;
-	@Inject
-	GroupInvitationFactory groupInvitationFactory;
-
-	// objects accessed from background threads need to be volatile
-	private volatile Waiter validationWaiter;
-	private volatile Waiter deliveryWaiter;
-
-	private final File testDir = TestUtils.getTestDirectory();
-	private final SecretKey master = TestUtils.getSecretKey();
-	private final int TIMEOUT = 15000;
-	private final String AUTHOR0 = "Author 0";
-	private final String AUTHOR1 = "Author 1";
-	private final String AUTHOR2 = "Author 2";
-
-	private static final Logger LOG =
-			Logger.getLogger(PrivateGroupManagerTest.class.getName());
-
-	private PrivateGroupManagerTestComponent t0, t1, t2;
-
-	@Rule
-	public ExpectedException thrown = ExpectedException.none();
+	private PrivateGroupManager groupManager0, groupManager1, groupManager2;
 
 	@Before
 	public void setUp() throws Exception {
-		PrivateGroupManagerTestComponent component =
-				DaggerPrivateGroupManagerTestComponent.builder().build();
-		component.inject(this);
-
-		assertTrue(testDir.mkdirs());
-		File t0Dir = new File(testDir, AUTHOR0);
-		t0 = DaggerPrivateGroupManagerTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
-		injectEagerSingletons(t0);
-		File t1Dir = new File(testDir, AUTHOR1);
-		t1 = DaggerPrivateGroupManagerTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
-		injectEagerSingletons(t1);
-		File t2Dir = new File(testDir, AUTHOR2);
-		t2 = DaggerPrivateGroupManagerTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
-		injectEagerSingletons(t2);
-
-		identityManager0 = t0.getIdentityManager();
-		identityManager1 = t1.getIdentityManager();
-		identityManager2 = t2.getIdentityManager();
-		contactManager0 = t0.getContactManager();
-		contactManager1 = t1.getContactManager();
-		contactManager2 = t2.getContactManager();
-		db0 = t0.getDatabaseComponent();
-		db1 = t1.getDatabaseComponent();
-		db2 = t2.getDatabaseComponent();
-		groupManager0 = t0.getPrivateGroupManager();
-		groupManager1 = t1.getPrivateGroupManager();
-		groupManager2 = t2.getPrivateGroupManager();
-		sync0 = t0.getSyncSessionFactory();
-		sync1 = t1.getSyncSessionFactory();
-		sync2 = t2.getSyncSessionFactory();
-
-		// initialize waiters fresh for each test
-		validationWaiter = new Waiter();
-		deliveryWaiter = new Waiter();
-
-		startLifecycles();
+		super.setUp();
+
+		groupManager0 = c0.getPrivateGroupManager();
+		groupManager1 = c1.getPrivateGroupManager();
+		groupManager2 = c2.getPrivateGroupManager();
+
+		privateGroup0 =
+				privateGroupFactory.createPrivateGroup("Testgroup", author0);
+		groupId0 = privateGroup0.getId();
 	}
 
 	@Test
 	public void testSendingMessage() throws Exception {
-		defaultInit();
+		addGroup();
 
 		// create and add test message
 		long time = clock.currentTimeMillis();
@@ -186,8 +65,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 				groupManager0.getPreviousMsgId(groupId0));
 
 		// sync test message
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync0To1(1, true);
 
 		// assert that message arrived as expected
 		Collection<GroupMessageHeader> headers =
@@ -213,7 +91,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testMessageWithWrongPreviousMsgId() throws Exception {
-		defaultInit();
+		addGroup();
 
 		// create and add test message with no previousMsgId
 		@SuppressWarnings("ConstantConditions")
@@ -223,8 +101,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		groupManager0.addLocalMessage(msg);
 
 		// sync test message
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// assert that message did not arrive
 		assertEquals(2, groupManager1.getHeaders(groupId0).size());
@@ -237,8 +114,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		groupManager0.addLocalMessage(msg);
 
 		// sync test message
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// assert that message did not arrive
 		assertEquals(2, groupManager1.getHeaders(groupId0).size());
@@ -251,8 +127,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		groupManager0.addLocalMessage(msg);
 
 		// sync test message
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// assert that message did not arrive
 		assertEquals(2, groupManager1.getHeaders(groupId0).size());
@@ -260,7 +135,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testMessageWithWrongParentMsgId() throws Exception {
-		defaultInit();
+		addGroup();
 
 		// create and add test message with random parentMsgId
 		MessageId parentMsgId = new MessageId(TestUtils.getRandomId());
@@ -271,8 +146,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		groupManager0.addLocalMessage(msg);
 
 		// sync test message
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// assert that message did not arrive
 		assertEquals(2, groupManager1.getHeaders(groupId0).size());
@@ -285,8 +159,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		groupManager0.addLocalMessage(msg);
 
 		// sync test message
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// assert that message did not arrive
 		assertEquals(2, groupManager1.getHeaders(groupId0).size());
@@ -294,7 +167,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testMessageWithWrongTimestamp() throws Exception {
-		defaultInit();
+		addGroup();
 
 		// create and add test message with wrong timestamp
 		MessageId previousMsgId = groupManager0.getPreviousMsgId(groupId0);
@@ -304,8 +177,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		groupManager0.addLocalMessage(msg);
 
 		// sync test message
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// assert that message did not arrive
 		assertEquals(2, groupManager1.getHeaders(groupId0).size());
@@ -318,8 +190,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		groupManager0.addLocalMessage(msg);
 
 		// sync test message
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync0To1(1, true);
 		assertEquals(3, groupManager1.getHeaders(groupId0).size());
 
 		// create and add test message with same timestamp as previous message
@@ -330,8 +201,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		groupManager0.addLocalMessage(msg);
 
 		// sync test message
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// assert that message did not arrive
 		assertEquals(3, groupManager1.getHeaders(groupId0).size());
@@ -339,10 +209,6 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testWrongJoinMessages1() throws Exception {
-		addDefaultIdentities();
-		addDefaultContacts();
-		listenToEvents();
-
 		// author0 joins privateGroup0 with wrong join message
 		long joinTime = clock.currentTimeMillis();
 		GroupMessage joinMsg0 = groupMessageFactory
@@ -354,14 +220,14 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 		// share the group with 1
 		Transaction txn0 = db0.startTransaction(false);
-		db0.setGroupVisibility(txn0, contactId1, privateGroup0.getId(), SHARED);
+		db0.setGroupVisibility(txn0, contactId1From0, privateGroup0.getId(), SHARED);
 		db0.commitTransaction(txn0);
 		db0.endTransaction(txn0);
 
 		// author1 joins privateGroup0 with wrong timestamp
 		joinTime = clock.currentTimeMillis();
 		long inviteTime = joinTime;
-		Contact c1 = contactManager0.getContact(contactId1);
+		Contact c1 = contactManager0.getContact(contactId1From0);
 		byte[] creatorSignature = groupInvitationFactory
 				.signInvitation(c1, privateGroup0.getId(), inviteTime,
 						author0.getPrivateKey());
@@ -374,20 +240,18 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 		// share the group with 0
 		Transaction txn1 = db1.startTransaction(false);
-		db1.setGroupVisibility(txn1, contactId01, privateGroup0.getId(),
+		db1.setGroupVisibility(txn1, contactId0From1, privateGroup0.getId(),
 				SHARED);
 		db1.commitTransaction(txn1);
 		db1.endTransaction(txn1);
 
 		// sync join messages
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// assert that 0 never joined the group from 1's perspective
 		assertEquals(1, groupManager1.getHeaders(groupId0).size());
 
-		sync1To0();
-		validationWaiter.await(TIMEOUT, 1);
+		sync1To0(1, false);
 
 		// assert that 1 never joined the group from 0's perspective
 		assertEquals(1, groupManager0.getHeaders(groupId0).size());
@@ -395,10 +259,6 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testWrongJoinMessages2() throws Exception {
-		addDefaultIdentities();
-		addDefaultContacts();
-		listenToEvents();
-
 		// author0 joins privateGroup0 with wrong member's join message
 		long joinTime = clock.currentTimeMillis();
 		long inviteTime = joinTime - 1;
@@ -417,7 +277,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 		// share the group with 1
 		Transaction txn0 = db0.startTransaction(false);
-		db0.setGroupVisibility(txn0, contactId1, privateGroup0.getId(), SHARED);
+		db0.setGroupVisibility(txn0, contactId1From0, privateGroup0.getId(), SHARED);
 		db0.commitTransaction(txn0);
 		db0.endTransaction(txn0);
 
@@ -425,7 +285,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		joinTime = clock.currentTimeMillis();
 		inviteTime = joinTime - 1;
 		// signature uses joiner's key, not creator's key
-		Contact c1 = contactManager0.getContact(contactId1);
+		Contact c1 = contactManager0.getContact(contactId1From0);
 		creatorSignature = groupInvitationFactory
 				.signInvitation(c1, privateGroup0.getId(), inviteTime,
 						author1.getPrivateKey());
@@ -438,19 +298,17 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 		// share the group with 0
 		Transaction txn1 = db1.startTransaction(false);
-		db1.setGroupVisibility(txn1, contactId01, privateGroup0.getId(), SHARED);
+		db1.setGroupVisibility(txn1, contactId0From1, privateGroup0.getId(), SHARED);
 		db1.commitTransaction(txn1);
 		db1.endTransaction(txn1);
 
 		// sync join messages
-		sync0To1();
-		validationWaiter.await(TIMEOUT, 1);
+		sync0To1(1, false);
 
 		// assert that 0 never joined the group from 1's perspective
 		assertEquals(1, groupManager1.getHeaders(groupId0).size());
 
-		sync1To0();
-		validationWaiter.await(TIMEOUT, 1);
+		sync1To0(1, false);
 
 		// assert that 1 never joined the group from 0's perspective
 		assertEquals(1, groupManager0.getHeaders(groupId0).size());
@@ -458,7 +316,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testGetMembers() throws Exception {
-		defaultInit();
+		addGroup();
 
 		Collection<GroupMember> members0 = groupManager0.getMembers(groupId0);
 		assertEquals(2, members0.size());
@@ -485,7 +343,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testJoinMessages() throws Exception {
-		defaultInit();
+		addGroup();
 
 		Collection<GroupMessageHeader> headers0 =
 				groupManager0.getHeaders(groupId0);
@@ -514,18 +372,18 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testRevealingRelationships() throws Exception {
-		defaultInit();
+		addGroup();
 
 		// share the group with 2
 		Transaction txn0 = db0.startTransaction(false);
-		db0.setGroupVisibility(txn0, contactId2, privateGroup0.getId(), SHARED);
+		db0.setGroupVisibility(txn0, contactId2From0, privateGroup0.getId(), SHARED);
 		db0.commitTransaction(txn0);
 		db0.endTransaction(txn0);
 
 		// author2 joins privateGroup0
 		long joinTime = clock.currentTimeMillis();
 		long inviteTime = joinTime - 1;
-		Contact c2 = contactManager0.getContact(contactId2);
+		Contact c2 = contactManager0.getContact(contactId2From0);
 		byte[] creatorSignature = groupInvitationFactory
 				.signInvitation(c2, privateGroup0.getId(), inviteTime,
 						author0.getPrivateKey());
@@ -536,18 +394,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		groupManager2.addPrivateGroup(txn2, privateGroup0, joinMsg2, false);
 
 		// share the group with 0
-		db2.setGroupVisibility(txn2, contactId01, privateGroup0.getId(),
+		db2.setGroupVisibility(txn2, contactId0From1, privateGroup0.getId(),
 				SHARED);
 		db2.commitTransaction(txn2);
 		db2.endTransaction(txn2);
 
 		// sync join messages
-		deliverMessage(sync2, contactId2, sync0, contactId02, "2 to 0");
-		deliveryWaiter.await(TIMEOUT, 1);
-		deliverMessage(sync0, contactId02, sync2, contactId2, "0 to 2");
-		deliveryWaiter.await(TIMEOUT, 2);
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
+		sync2To0(1, true);
+		sync0To2(2, true);
+		sync0To1(1, true);
 
 		// check that everybody sees everybody else as joined
 		Collection<GroupMember> members0 = groupManager0.getMembers(groupId0);
@@ -626,7 +481,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 	@Test
 	public void testDissolveGroup() throws Exception {
-		defaultInit();
+		addGroup();
 
 		// group is not dissolved initially
 		assertFalse(groupManager1.isDissolved(groupId0));
@@ -641,93 +496,6 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		assertTrue(groupManager1.isDissolved(groupId0));
 	}
 
-	@After
-	public void tearDown() throws Exception {
-		stopLifecycles();
-		TestUtils.deleteTestDirectory(testDir);
-	}
-
-	private class Listener implements EventListener {
-		@Override
-		public void eventOccurred(Event e) {
-			if (e instanceof MessageStateChangedEvent) {
-				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
-				if (!event.isLocal()) {
-					if (event.getState() == DELIVERED) {
-						LOG.info("Delivered new message");
-						deliveryWaiter.resume();
-					} else if (event.getState() == INVALID ||
-							event.getState() == PENDING) {
-						LOG.info("Validated new " + event.getState().name() +
-								" message");
-						validationWaiter.resume();
-					}
-				}
-			}
-		}
-	}
-
-	private void defaultInit() throws Exception {
-		addDefaultIdentities();
-		addDefaultContacts();
-		listenToEvents();
-		addGroup();
-	}
-
-	private void addDefaultIdentities() throws DbException {
-		KeyPair keyPair0 = crypto.generateSignatureKeyPair();
-		byte[] publicKey0 = keyPair0.getPublic().getEncoded();
-		byte[] privateKey0 = keyPair0.getPrivate().getEncoded();
-		author0 = authorFactory
-				.createLocalAuthor(AUTHOR0, publicKey0, privateKey0);
-		identityManager0.registerLocalAuthor(author0);
-		privateGroup0 =
-				privateGroupFactory.createPrivateGroup("Testgroup", author0);
-		groupId0 = privateGroup0.getId();
-
-		KeyPair keyPair1 = crypto.generateSignatureKeyPair();
-		byte[] publicKey1 = keyPair1.getPublic().getEncoded();
-		byte[] privateKey1 = keyPair1.getPrivate().getEncoded();
-		author1 = authorFactory
-				.createLocalAuthor(AUTHOR1, publicKey1, privateKey1);
-		identityManager1.registerLocalAuthor(author1);
-
-		KeyPair keyPair2 = crypto.generateSignatureKeyPair();
-		byte[] publicKey2 = keyPair2.getPublic().getEncoded();
-		byte[] privateKey2 = keyPair2.getPrivate().getEncoded();
-		author2 = authorFactory
-				.createLocalAuthor(AUTHOR2, publicKey2, privateKey2);
-		identityManager2.registerLocalAuthor(author2);
-	}
-
-	private void addDefaultContacts() throws DbException {
-		// creator adds invitee as contact
-		contactId1 = contactManager0
-				.addContact(author1, author0.getId(), master,
-						clock.currentTimeMillis(), true, true, true);
-		// invitee adds creator back
-		contactId01 = contactManager1
-				.addContact(author0, author1.getId(), master,
-						clock.currentTimeMillis(), true, true, true);
-		// creator adds invitee as contact
-		contactId2 = contactManager0
-				.addContact(author2, author0.getId(), master,
-						clock.currentTimeMillis(), true, true, true);
-		// invitee adds creator back
-		contactId02 = contactManager2
-				.addContact(author0, author2.getId(), master,
-						clock.currentTimeMillis(), true, true, true);
-	}
-
-	private void listenToEvents() {
-		Listener listener0 = new Listener();
-		t0.getEventBus().addListener(listener0);
-		Listener listener1 = new Listener();
-		t1.getEventBus().addListener(listener1);
-		Listener listener2 = new Listener();
-		t2.getEventBus().addListener(listener2);
-	}
-
 	private void addGroup() throws Exception {
 		// author0 joins privateGroup0
 		long joinTime = clock.currentTimeMillis();
@@ -739,14 +507,14 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 		// share the group with 1
 		Transaction txn0 = db0.startTransaction(false);
-		db0.setGroupVisibility(txn0, contactId1, privateGroup0.getId(), SHARED);
+		db0.setGroupVisibility(txn0, contactId1From0, privateGroup0.getId(), SHARED);
 		db0.commitTransaction(txn0);
 		db0.endTransaction(txn0);
 
 		// author1 joins privateGroup0
 		joinTime = clock.currentTimeMillis();
 		long inviteTime = joinTime - 1;
-		Contact c1 = contactManager0.getContact(contactId1);
+		Contact c1 = contactManager0.getContact(contactId1From0);
 		byte[] creatorSignature = groupInvitationFactory
 				.signInvitation(c1, privateGroup0.getId(), inviteTime,
 						author0.getPrivateKey());
@@ -757,7 +525,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
 		// share the group with 0
 		Transaction txn1 = db1.startTransaction(false);
-		db1.setGroupVisibility(txn1, contactId01, privateGroup0.getId(),
+		db1.setGroupVisibility(txn1, contactId0From1, privateGroup0.getId(),
 				SHARED);
 		db1.commitTransaction(txn1);
 		db1.endTransaction(txn1);
@@ -765,74 +533,8 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 				groupManager1.getPreviousMsgId(groupId0));
 
 		// sync join messages
-		sync0To1();
-		deliveryWaiter.await(TIMEOUT, 1);
-		sync1To0();
-		deliveryWaiter.await(TIMEOUT, 1);
-	}
-
-	private void sync0To1() throws IOException, TimeoutException {
-		deliverMessage(sync0, contactId01, sync1, contactId1, "0 to 1");
-	}
-
-	private void sync1To0() throws IOException, TimeoutException {
-		deliverMessage(sync1, contactId1, sync0, contactId01, "1 to 0");
-	}
-
-	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
-			SyncSessionFactory toSync, ContactId toId, String debug)
-			throws IOException, TimeoutException {
-
-		if (debug != null) LOG.info("TEST: Sending message from " + debug);
-
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		// Create an outgoing sync session
-		SyncSession sessionFrom =
-				fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
-		// Write whatever needs to be written
-		sessionFrom.run();
-		out.close();
-
-		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-		// Create an incoming sync session
-		SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
-		// Read whatever needs to be read
-		sessionTo.run();
-		in.close();
-	}
-
-	private void startLifecycles() throws InterruptedException {
-		// Start the lifecycle manager and wait for it to finish
-		lifecycleManager0 = t0.getLifecycleManager();
-		lifecycleManager1 = t1.getLifecycleManager();
-		lifecycleManager2 = t2.getLifecycleManager();
-		lifecycleManager0.startServices(AUTHOR0);
-		lifecycleManager1.startServices(AUTHOR1);
-		lifecycleManager2.startServices(AUTHOR2);
-		lifecycleManager0.waitForStartup();
-		lifecycleManager1.waitForStartup();
-		lifecycleManager2.waitForStartup();
-	}
-
-	private void stopLifecycles() throws InterruptedException {
-		// Clean up
-		lifecycleManager0.stopServices();
-		lifecycleManager1.stopServices();
-		lifecycleManager2.stopServices();
-		lifecycleManager0.waitForShutdown();
-		lifecycleManager1.waitForShutdown();
-		lifecycleManager2.waitForShutdown();
-	}
-
-	private void injectEagerSingletons(
-			PrivateGroupManagerTestComponent component) {
-		component.inject(new LifecycleModule.EagerSingletons());
-		component.inject(new PrivateGroupModule.EagerSingletons());
-		component.inject(new CryptoModule.EagerSingletons());
-		component.inject(new ContactModule.EagerSingletons());
-		component.inject(new TransportModule.EagerSingletons());
-		component.inject(new SyncModule.EagerSingletons());
-		component.inject(new PropertiesModule.EagerSingletons());
+		sync0To1(1, true);
+		sync1To0(1, true);
 	}
 
 }
diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java
deleted file mode 100644
index 08bdfd8a6b0dc1951a2a5ae0aacfa8ea46334fbc..0000000000000000000000000000000000000000
--- a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTestComponent.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.briarproject;
-
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.db.DatabaseComponent;
-import org.briarproject.api.event.EventBus;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.privategroup.PrivateGroupManager;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.clients.ClientsModule;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.data.DataModule;
-import org.briarproject.db.DatabaseModule;
-import org.briarproject.event.EventModule;
-import org.briarproject.identity.IdentityModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.messaging.MessagingModule;
-import org.briarproject.privategroup.PrivateGroupModule;
-import org.briarproject.privategroup.invitation.GroupInvitationModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sharing.SharingModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.system.SystemModule;
-import org.briarproject.transport.TransportModule;
-
-import javax.inject.Singleton;
-
-import dagger.Component;
-
-@Singleton
-@Component(modules = {
-		TestDatabaseModule.class,
-		TestPluginsModule.class,
-		TestSeedProviderModule.class,
-		ClientsModule.class,
-		ContactModule.class,
-		CryptoModule.class,
-		DataModule.class,
-		DatabaseModule.class,
-		EventModule.class,
-		MessagingModule.class,
-		PrivateGroupModule.class,
-		GroupInvitationModule.class,
-		IdentityModule.class,
-		LifecycleModule.class,
-		PropertiesModule.class,
-		SharingModule.class,
-		SyncModule.class,
-		SystemModule.class,
-		TransportModule.class
-})
-interface PrivateGroupManagerTestComponent {
-
-	void inject(PrivateGroupManagerTest testCase);
-
-	void inject(ContactModule.EagerSingletons init);
-
-	void inject(CryptoModule.EagerSingletons init);
-
-	void inject(PrivateGroupModule.EagerSingletons init);
-
-	void inject(LifecycleModule.EagerSingletons init);
-
-	void inject(PropertiesModule.EagerSingletons init);
-
-	void inject(SyncModule.EagerSingletons init);
-
-	void inject(TransportModule.EagerSingletons init);
-
-	LifecycleManager getLifecycleManager();
-
-	EventBus getEventBus();
-
-	IdentityManager getIdentityManager();
-
-	ContactManager getContactManager();
-
-	PrivateGroupManager getPrivateGroupManager();
-
-	SyncSessionFactory getSyncSessionFactory();
-
-	DatabaseComponent getDatabaseComponent();
-
-}
diff --git a/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTest.java
index 826524cc18ae7f9a0d86ca37befe6bd924827e5d..44699899e41a1b54f20cbba2f676d72a6057f4f9 100644
--- a/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTest.java
@@ -1,26 +1,19 @@
 package org.briarproject.introduction;
 
-import android.support.annotation.Nullable;
-
 import net.jodah.concurrentunit.Waiter;
 
 import org.briarproject.BriarIntegrationTest;
-import org.briarproject.TestDatabaseModule;
 import org.briarproject.TestUtils;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.clients.MessageTracker;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.KeyPair;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfEntry;
 import org.briarproject.api.data.BdfList;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Metadata;
 import org.briarproject.api.db.Transaction;
@@ -30,37 +23,17 @@ import org.briarproject.api.event.IntroductionAbortedEvent;
 import org.briarproject.api.event.IntroductionRequestReceivedEvent;
 import org.briarproject.api.event.IntroductionResponseReceivedEvent;
 import org.briarproject.api.event.IntroductionSucceededEvent;
-import org.briarproject.api.event.MessageStateChangedEvent;
-import org.briarproject.api.identity.AuthorFactory;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.introduction.IntroductionManager;
 import org.briarproject.api.introduction.IntroductionMessage;
 import org.briarproject.api.introduction.IntroductionRequest;
-import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.properties.TransportProperties;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
-import org.briarproject.api.sync.SyncSession;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.api.sync.ValidationManager.State;
-import org.briarproject.api.system.Clock;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.system.SystemModule;
-import org.briarproject.transport.TransportModule;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
@@ -72,9 +45,6 @@ import java.util.Map.Entry;
 import java.util.concurrent.TimeoutException;
 import java.util.logging.Logger;
 
-import javax.inject.Inject;
-
-import static org.briarproject.TestPluginsModule.MAX_LATENCY;
 import static org.briarproject.TestPluginsModule.TRANSPORT_ID;
 import static org.briarproject.api.clients.MessageQueueManager.QUEUE_STATE_KEY;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -92,8 +62,6 @@ import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
-import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
-import static org.briarproject.api.sync.ValidationManager.State.INVALID;
 import static org.briarproject.introduction.IntroduceeManager.SIGNING_LABEL_RESPONSE;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -102,38 +70,12 @@ import static org.junit.Assert.fail;
 
 public class IntroductionIntegrationTest extends BriarIntegrationTest {
 
-	private LifecycleManager lifecycleManager0, lifecycleManager1,
-			lifecycleManager2;
-	private SyncSessionFactory sync0, sync1, sync2;
-	private ContactManager contactManager0, contactManager1, contactManager2;
-	private MessageTracker messageTracker0, messageTracker1, messageTracker2;
-	private ContactId contactId0, contactId1, contactId2;
-	private IdentityManager identityManager0, identityManager1,
-			identityManager2;
-	private LocalAuthor author0, author1, author2;
-
-	@Inject
-	Clock clock;
-	@Inject
-	CryptoComponent crypto;
-	@Inject
-	AuthorFactory authorFactory;
-	@Inject
-	IntroductionGroupFactory introductionGroupFactory;
-
 	// objects accessed from background threads need to be volatile
 	private volatile IntroductionManager introductionManager0;
 	private volatile IntroductionManager introductionManager1;
 	private volatile IntroductionManager introductionManager2;
 	private volatile Waiter eventWaiter;
-	private volatile Waiter msgWaiter;
-
-	private final File testDir = TestUtils.getTestDirectory();
-	private final SecretKey master = TestUtils.getSecretKey();
-	private final int TIMEOUT = 15000;
-	private final String INTRODUCER = "Introducer";
-	private final String INTRODUCEE1 = "Introducee1";
-	private final String INTRODUCEE2 = "Introducee2";
+
 	private IntroducerListener listener0;
 	private IntroduceeListener listener1;
 	private IntroduceeListener listener2;
@@ -141,361 +83,288 @@ public class IntroductionIntegrationTest extends BriarIntegrationTest {
 	private static final Logger LOG =
 			Logger.getLogger(IntroductionIntegrationTest.class.getName());
 
-	private IntroductionIntegrationTestComponent t0, t1, t2;
-
 	interface StateVisitor {
 		boolean visit(BdfDictionary response);
 	}
 
 	@Before
-	public void setUp() {
-		IntroductionIntegrationTestComponent component =
-				DaggerIntroductionIntegrationTestComponent.builder().build();
-		component.inject(this);
-		injectEagerSingletons(component);
-
-		assertTrue(testDir.mkdirs());
-		File t0Dir = new File(testDir, INTRODUCER);
-		t0 = DaggerIntroductionIntegrationTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
-		injectEagerSingletons(t0);
-		File t1Dir = new File(testDir, INTRODUCEE1);
-		t1 = DaggerIntroductionIntegrationTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
-		injectEagerSingletons(t1);
-		File t2Dir = new File(testDir, INTRODUCEE2);
-		t2 = DaggerIntroductionIntegrationTestComponent.builder()
-				.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
-		injectEagerSingletons(t2);
-
-		identityManager0 = t0.getIdentityManager();
-		identityManager1 = t1.getIdentityManager();
-		identityManager2 = t2.getIdentityManager();
-		contactManager0 = t0.getContactManager();
-		contactManager1 = t1.getContactManager();
-		contactManager2 = t2.getContactManager();
-		messageTracker0 = t0.getMessageTracker();
-		messageTracker1 = t1.getMessageTracker();
-		messageTracker2 = t2.getMessageTracker();
-		introductionManager0 = t0.getIntroductionManager();
-		introductionManager1 = t1.getIntroductionManager();
-		introductionManager2 = t2.getIntroductionManager();
-		sync0 = t0.getSyncSessionFactory();
-		sync1 = t1.getSyncSessionFactory();
-		sync2 = t2.getSyncSessionFactory();
-
-		// initialize waiters fresh for each test
+	public void setUp() throws Exception {
+		super.setUp();
+
+		introductionManager0 = c0.getIntroductionManager();
+		introductionManager1 = c1.getIntroductionManager();
+		introductionManager2 = c2.getIntroductionManager();
+
+		// initialize waiter fresh for each test
 		eventWaiter = new Waiter();
-		msgWaiter = new Waiter();
+
+		addTransportProperties();
 	}
 
 	@Test
 	public void testIntroductionSession() throws Exception {
-		startLifecycles();
-		try {
-			getDefaultIdentities();
-			addDefaultContacts();
-			addListeners(true, true);
-			addTransportProperties();
-
-			// make introduction
-			long time = clock.currentTimeMillis();
-			Contact introducee1 = contactManager0.getContact(contactId1);
-			Contact introducee2 = contactManager0.getContact(contactId2);
-			introductionManager0
-					.makeIntroduction(introducee1, introducee2, "Hi!", time);
-
-			// check that messages are tracked properly
-			Group g1 = introductionGroupFactory
-					.createIntroductionGroup(introducee1);
-			Group g2 = introductionGroupFactory
-					.createIntroductionGroup(introducee2);
-			assertGroupCount(messageTracker0, g1.getId(), 1, 0, time);
-			assertGroupCount(messageTracker0, g2.getId(), 1, 0, time);
-
-			// sync first request message
-			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
-			assertGroupCount(messageTracker1, g1.getId(), 2, 1);
-
-			// sync second request message
-			deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener2.requestReceived);
-			assertGroupCount(messageTracker2, g2.getId(), 2, 1);
-
-			// sync first response
-			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.response1Received);
-			assertGroupCount(messageTracker0, g1.getId(), 2, 1);
-
-			// sync second response
-			deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.response2Received);
-			assertGroupCount(messageTracker0, g2.getId(), 2, 1);
-
-			// sync forwarded responses to introducees
-			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-			deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
-			assertGroupCount(messageTracker1, g1.getId(), 2, 1);
-			assertGroupCount(messageTracker2, g2.getId(), 2, 1);
-
-			// sync first ACK and its forward
-			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
-			deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
-
-			// sync second ACK and its forward
-			deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
-			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 2");
-
-			// wait for introduction to succeed
-			eventWaiter.await(TIMEOUT, 2);
-			assertTrue(listener1.succeeded);
-			assertTrue(listener2.succeeded);
-
-			assertTrue(contactManager1
-					.contactExists(author2.getId(), author1.getId()));
-			assertTrue(contactManager2
-					.contactExists(author1.getId(), author2.getId()));
-
-			// make sure that introduced contacts are not verified
-			for (Contact c : contactManager1.getActiveContacts()) {
-				if (c.getAuthor().equals(author2)) {
-					assertFalse(c.isVerified());
-				}
+		addListeners(true, true);
+
+		// make introduction
+		long time = clock.currentTimeMillis();
+		Contact introducee1 = contact1From0;
+		Contact introducee2 = contact2From0;
+		introductionManager0
+				.makeIntroduction(introducee1, introducee2, "Hi!", time);
+
+		// check that messages are tracked properly
+		Group g1 = introductionGroupFactory
+				.createIntroductionGroup(introducee1);
+		Group g2 = introductionGroupFactory
+				.createIntroductionGroup(introducee2);
+		assertGroupCount(messageTracker0, g1.getId(), 1, 0, time);
+		assertGroupCount(messageTracker0, g2.getId(), 1, 0, time);
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+		assertGroupCount(messageTracker1, g1.getId(), 2, 1);
+
+		// sync second request message
+		sync0To2(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener2.requestReceived);
+		assertGroupCount(messageTracker2, g2.getId(), 2, 1);
+
+		// sync first response
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response1Received);
+		assertGroupCount(messageTracker0, g1.getId(), 2, 1);
+
+		// sync second response
+		sync2To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response2Received);
+		assertGroupCount(messageTracker0, g2.getId(), 2, 1);
+
+		// sync forwarded responses to introducees
+		sync0To1(1, true);
+		sync0To2(1, true);
+		assertGroupCount(messageTracker1, g1.getId(), 2, 1);
+		assertGroupCount(messageTracker2, g2.getId(), 2, 1);
+
+		// sync first ACK and its forward
+		sync1To0(1, true);
+		sync0To2(1, true);
+
+		// sync second ACK and its forward
+		sync2To0(1, true);
+		sync0To1(1, true);
+
+		// wait for introduction to succeed
+		eventWaiter.await(TIMEOUT, 2);
+		assertTrue(listener1.succeeded);
+		assertTrue(listener2.succeeded);
+
+		assertTrue(contactManager1
+				.contactExists(author2.getId(), author1.getId()));
+		assertTrue(contactManager2
+				.contactExists(author1.getId(), author2.getId()));
+
+		// make sure that introduced contacts are not verified
+		for (Contact c : contactManager1.getActiveContacts()) {
+			if (c.getAuthor().equals(author2)) {
+				assertFalse(c.isVerified());
 			}
-			for (Contact c : contactManager2.getActiveContacts()) {
-				if (c.getAuthor().equals(author1)) {
-					assertFalse(c.isVerified());
-				}
+		}
+		for (Contact c : contactManager2.getActiveContacts()) {
+			if (c.getAuthor().equals(author1)) {
+				assertFalse(c.isVerified());
 			}
-
-			assertDefaultUiMessages();
-			assertGroupCount(messageTracker0, g1.getId(), 2, 1);
-			assertGroupCount(messageTracker0, g2.getId(), 2, 1);
-			assertGroupCount(messageTracker1, g1.getId(), 2, 1);
-			assertGroupCount(messageTracker2, g2.getId(), 2, 1);
-		} finally {
-			stopLifecycles();
 		}
+
+		assertDefaultUiMessages();
+		assertGroupCount(messageTracker0, g1.getId(), 2, 1);
+		assertGroupCount(messageTracker0, g2.getId(), 2, 1);
+		assertGroupCount(messageTracker1, g1.getId(), 2, 1);
+		assertGroupCount(messageTracker2, g2.getId(), 2, 1);
 	}
 
 	@Test
 	public void testIntroductionSessionFirstDecline() throws Exception {
-		startLifecycles();
-		try {
-			getDefaultIdentities();
-			addDefaultContacts();
-			addListeners(false, true);
-			addTransportProperties();
-
-			// make introduction
-			long time = clock.currentTimeMillis();
-			Contact introducee1 = contactManager0.getContact(contactId1);
-			Contact introducee2 = contactManager0.getContact(contactId2);
-			introductionManager0
-					.makeIntroduction(introducee1, introducee2, null, time);
-
-			// sync request messages
-			deliverMessage(sync0, contactId0, sync1, contactId1);
-			deliverMessage(sync0, contactId0, sync2, contactId2);
-
-			// wait for requests to arrive
-			eventWaiter.await(TIMEOUT, 2);
-			assertTrue(listener1.requestReceived);
-			assertTrue(listener2.requestReceived);
-
-			// sync first response
-			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.response1Received);
-
-			// sync second response
-			deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.response2Received);
-
-			// sync first forwarded response
-			deliverMessage(sync0, contactId0, sync2, contactId2);
-
-			// note how the introducer does not forward the second response,
-			// because after the first decline the protocol finished
-
-			assertFalse(listener1.succeeded);
-			assertFalse(listener2.succeeded);
-
-			assertFalse(contactManager1
-					.contactExists(author2.getId(), author1.getId()));
-			assertFalse(contactManager2
-					.contactExists(author1.getId(), author2.getId()));
-
-			Group g1 = introductionGroupFactory
-					.createIntroductionGroup(introducee1);
-			Group g2 = introductionGroupFactory
-					.createIntroductionGroup(introducee2);
-			assertEquals(2,
-					introductionManager0.getIntroductionMessages(contactId1)
-							.size());
-			assertGroupCount(messageTracker0, g1.getId(), 2, 1);
-			assertEquals(2,
-					introductionManager0.getIntroductionMessages(contactId2)
-							.size());
-			assertGroupCount(messageTracker0, g2.getId(), 2, 1);
-			assertEquals(2,
-					introductionManager1.getIntroductionMessages(contactId0)
-							.size());
-			assertGroupCount(messageTracker1, g1.getId(), 2, 1);
-			// introducee2 should also have the decline response of introducee1
-			assertEquals(3,
-					introductionManager2.getIntroductionMessages(contactId0)
-							.size());
-			assertGroupCount(messageTracker2, g2.getId(), 3, 2);
-		} finally {
-			stopLifecycles();
-		}
+		addListeners(false, true);
+
+		// make introduction
+		long time = clock.currentTimeMillis();
+		Contact introducee1 = contact1From0;
+		Contact introducee2 = contact2From0;
+		introductionManager0
+				.makeIntroduction(introducee1, introducee2, null, time);
+
+		// sync request messages
+		sync0To1(1, true);
+		sync0To2(1, true);
+
+		// wait for requests to arrive
+		eventWaiter.await(TIMEOUT, 2);
+		assertTrue(listener1.requestReceived);
+		assertTrue(listener2.requestReceived);
+
+		// sync first response
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response1Received);
+
+		// sync second response
+		sync2To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response2Received);
+
+		// sync first forwarded response
+		sync0To2(1, true);
+
+		// note how the introducer does not forward the second response,
+		// because after the first decline the protocol finished
+
+		assertFalse(listener1.succeeded);
+		assertFalse(listener2.succeeded);
+
+		assertFalse(contactManager1
+				.contactExists(author2.getId(), author1.getId()));
+		assertFalse(contactManager2
+				.contactExists(author1.getId(), author2.getId()));
+
+		Group g1 = introductionGroupFactory
+				.createIntroductionGroup(introducee1);
+		Group g2 = introductionGroupFactory
+				.createIntroductionGroup(introducee2);
+		assertEquals(2,
+				introductionManager0.getIntroductionMessages(contactId1From0)
+						.size());
+		assertGroupCount(messageTracker0, g1.getId(), 2, 1);
+		assertEquals(2,
+				introductionManager0.getIntroductionMessages(contactId2From0)
+						.size());
+		assertGroupCount(messageTracker0, g2.getId(), 2, 1);
+		assertEquals(2,
+				introductionManager1.getIntroductionMessages(contactId0From1)
+						.size());
+		assertGroupCount(messageTracker1, g1.getId(), 2, 1);
+		// introducee2 should also have the decline response of introducee1
+		assertEquals(3,
+				introductionManager2.getIntroductionMessages(contactId0From2)
+						.size());
+		assertGroupCount(messageTracker2, g2.getId(), 3, 2);
 	}
 
 	@Test
 	public void testIntroductionSessionSecondDecline() throws Exception {
-		startLifecycles();
-		try {
-			getDefaultIdentities();
-			addDefaultContacts();
-			addListeners(true, false);
-			addTransportProperties();
-
-			// make introduction
-			long time = clock.currentTimeMillis();
-			Contact introducee1 = contactManager0.getContact(contactId1);
-			Contact introducee2 = contactManager0.getContact(contactId2);
-			introductionManager0
-					.makeIntroduction(introducee1, introducee2, null, time);
-
-			// sync request messages
-			deliverMessage(sync0, contactId0, sync1, contactId1);
-			deliverMessage(sync0, contactId0, sync2, contactId2);
-
-			// wait for requests to arrive
-			eventWaiter.await(TIMEOUT, 2);
-			assertTrue(listener1.requestReceived);
-			assertTrue(listener2.requestReceived);
-
-			// sync first response
-			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.response1Received);
-
-			// sync second response
-			deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.response2Received);
-
-			// sync both forwarded response
-			deliverMessage(sync0, contactId0, sync2, contactId2);
-			deliverMessage(sync0, contactId0, sync1, contactId1);
-
-			assertFalse(contactManager1
-					.contactExists(author2.getId(), author1.getId()));
-			assertFalse(contactManager2
-					.contactExists(author1.getId(), author2.getId()));
-
-			assertEquals(2,
-					introductionManager0.getIntroductionMessages(contactId1)
-							.size());
-			assertEquals(2,
-					introductionManager0.getIntroductionMessages(contactId2)
-							.size());
-			// introducee1 also sees the decline response from introducee2
-			assertEquals(3,
-					introductionManager1.getIntroductionMessages(contactId0)
-							.size());
-			assertEquals(2,
-					introductionManager2.getIntroductionMessages(contactId0)
-							.size());
-		} finally {
-			stopLifecycles();
-		}
+		addListeners(true, false);
+
+		// make introduction
+		long time = clock.currentTimeMillis();
+		introductionManager0
+				.makeIntroduction(contact1From0, contact2From0, null, time);
+
+		// sync request messages
+		sync0To1(1, true);
+		sync0To2(1, true);
+
+		// wait for requests to arrive
+		eventWaiter.await(TIMEOUT, 2);
+		assertTrue(listener1.requestReceived);
+		assertTrue(listener2.requestReceived);
+
+		// sync first response
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response1Received);
+
+		// sync second response
+		sync2To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response2Received);
+
+		// sync both forwarded response
+		sync0To2(1, true);
+		sync0To1(1, true);
+
+		assertFalse(contactManager1
+				.contactExists(author2.getId(), author1.getId()));
+		assertFalse(contactManager2
+				.contactExists(author1.getId(), author2.getId()));
+
+		assertEquals(2,
+				introductionManager0.getIntroductionMessages(contactId1From0)
+						.size());
+		assertEquals(2,
+				introductionManager0.getIntroductionMessages(contactId2From0)
+						.size());
+		// introducee1 also sees the decline response from introducee2
+		assertEquals(3,
+				introductionManager1.getIntroductionMessages(contactId0From1)
+						.size());
+		assertEquals(2,
+				introductionManager2.getIntroductionMessages(contactId0From2)
+						.size());
 	}
 
 	@Test
 	public void testIntroductionSessionDelayedFirstDecline() throws Exception {
-		startLifecycles();
-		try {
-			getDefaultIdentities();
-			addDefaultContacts();
-			addListeners(false, false);
-			addTransportProperties();
-
-			// make introduction
-			long time = clock.currentTimeMillis();
-			Contact introducee1 = contactManager0.getContact(contactId1);
-			Contact introducee2 = contactManager0.getContact(contactId2);
-			introductionManager0
-					.makeIntroduction(introducee1, introducee2, null, time);
-
-			// sync request messages
-			deliverMessage(sync0, contactId0, sync1, contactId1);
-			deliverMessage(sync0, contactId0, sync2, contactId2);
-
-			// wait for requests to arrive
-			eventWaiter.await(TIMEOUT, 2);
-			assertTrue(listener1.requestReceived);
-			assertTrue(listener2.requestReceived);
-
-			// sync first response
-			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.response1Received);
-
-			// sync second response
-			deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener0.response2Received);
-
-			// sync first forwarded response
-			deliverMessage(sync0, contactId0, sync2, contactId2);
-
-			// note how the second response will not be forwarded anymore
-
-			assertFalse(contactManager1
-					.contactExists(author2.getId(), author1.getId()));
-			assertFalse(contactManager2
-					.contactExists(author1.getId(), author2.getId()));
-
-			// since introducee2 was already in FINISHED state when
-			// introducee1's response arrived, she ignores and deletes it
-			assertDefaultUiMessages();
-		} finally {
-			stopLifecycles();
-		}
+		addListeners(false, false);
+
+		// make introduction
+		long time = clock.currentTimeMillis();
+		introductionManager0
+				.makeIntroduction(contact1From0, contact2From0, null, time);
+
+		// sync request messages
+		sync0To1(1, true);
+		sync0To2(1, true);
+
+		// wait for requests to arrive
+		eventWaiter.await(TIMEOUT, 2);
+		assertTrue(listener1.requestReceived);
+		assertTrue(listener2.requestReceived);
+
+		// sync first response
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response1Received);
+
+		// sync second response
+		sync2To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response2Received);
+
+		// sync first forwarded response
+		sync0To2(1, true);
+
+		// note how the second response will not be forwarded anymore
+
+		assertFalse(contactManager1
+				.contactExists(author2.getId(), author1.getId()));
+		assertFalse(contactManager2
+				.contactExists(author1.getId(), author2.getId()));
+
+		// since introducee2 was already in FINISHED state when
+		// introducee1's response arrived, she ignores and deletes it
+		assertDefaultUiMessages();
 	}
 
 	@Test
 	public void testResponseAndAckInOneSession() throws Exception {
-		startLifecycles();
-
-		getDefaultIdentities();
-		addDefaultContacts();
 		addListeners(true, true);
-		addTransportProperties();
 
 		// make introduction
 		long time = clock.currentTimeMillis();
-		Contact introducee1 = contactManager0.getContact(contactId1);
-		Contact introducee2 = contactManager0.getContact(contactId2);
 		introductionManager0
-				.makeIntroduction(introducee1, introducee2, "Hi!", time);
+				.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
 
 		// sync first request message
-		deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
+		sync0To1(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener1.requestReceived);
 
 		// sync first response
-		deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
+		sync1To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.response1Received);
 
@@ -504,355 +373,293 @@ public class IntroductionIntegrationTest extends BriarIntegrationTest {
 		listener2.answerRequests = false;
 
 		// sync second request message and first response
-		deliverMessage(sync0, contactId0, sync2, contactId2, 2, "0 to 2");
+		sync0To2(2, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener2.requestReceived);
 
 		// answer request manually
 		introductionManager2
-				.acceptIntroduction(contactId0, listener2.sessionId, time);
+				.acceptIntroduction(contactId0From2, listener2.sessionId, time);
 
 		// sync second response and ACK and make sure there is no abort
-		deliverMessage(sync2, contactId2, sync0, contactId0, 2, "2 to 0");
+		sync2To0(2, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.response2Received);
 		assertFalse(listener0.aborted);
-
-		stopLifecycles();
 	}
 
 	@Test
 	public void testIntroductionToSameContact() throws Exception {
-		startLifecycles();
-		try {
-			getDefaultIdentities();
-			addDefaultContacts();
-			addListeners(true, false);
-			addTransportProperties();
-
-			// make introduction
-			long time = clock.currentTimeMillis();
-			Contact introducee1 = contactManager0.getContact(contactId1);
-			introductionManager0
-					.makeIntroduction(introducee1, introducee1, null, time);
-
-			// sync request messages
-			deliverMessage(sync0, contactId0, sync1, contactId1);
-
-			// we should not get any event, because the request will be discarded
-			assertFalse(listener1.requestReceived);
-
-			// make really sure we don't have that request
-			assertTrue(introductionManager1.getIntroductionMessages(contactId0)
-					.isEmpty());
-		} finally {
-			stopLifecycles();
-		}
+		addListeners(true, false);
+
+		// make introduction
+		long time = clock.currentTimeMillis();
+		introductionManager0
+				.makeIntroduction(contact1From0, contact1From0, null, time);
+
+		// sync request messages
+		sync0To1(1, false);
+
+		// we should not get any event, because the request will be discarded
+		assertFalse(listener1.requestReceived);
+
+		// make really sure we don't have that request
+		assertTrue(introductionManager1.getIntroductionMessages(contactId0From1)
+				.isEmpty());
 	}
 
 	@Test
 	public void testSessionIdReuse() throws Exception {
-		startLifecycles();
-		try {
-			getDefaultIdentities();
-			addDefaultContacts();
-			addListeners(true, true);
-			addTransportProperties();
-
-			// make introduction
-			long time = clock.currentTimeMillis();
-			Contact introducee1 = contactManager0.getContact(contactId1);
-			Contact introducee2 = contactManager0.getContact(contactId2);
-			introductionManager0
-					.makeIntroduction(introducee1, introducee2, "Hi!", time);
-
-			// sync first request message
-			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
-
-			// get SessionId
-			List<IntroductionMessage> list = new ArrayList<>(
-					introductionManager1.getIntroductionMessages(contactId0));
-			assertEquals(2, list.size());
-			assertTrue(list.get(0) instanceof IntroductionRequest);
-			IntroductionRequest msg = (IntroductionRequest) list.get(0);
-			SessionId sessionId = msg.getSessionId();
-
-			// get contact group
-			IntroductionGroupFactory groupFactory =
-					t0.getIntroductionGroupFactory();
-			Group group = groupFactory.createIntroductionGroup(introducee1);
-
-			// create new message with same SessionId
-			BdfDictionary d = BdfDictionary.of(
-					new BdfEntry(TYPE, TYPE_REQUEST),
-					new BdfEntry(SESSION_ID, sessionId),
-					new BdfEntry(GROUP_ID, group.getId()),
-					new BdfEntry(NAME, TestUtils.getRandomString(42)),
-					new BdfEntry(PUBLIC_KEY,
-							TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH))
-			);
-
-			// reset request received state
-			listener1.requestReceived = false;
-
-			// add the message to the queue
-			DatabaseComponent db0 = t0.getDatabaseComponent();
-			MessageSender sender0 = t0.getMessageSender();
-			Transaction txn = db0.startTransaction(false);
-			try {
-				sender0.sendMessage(txn, d);
-				db0.commitTransaction(txn);
-			} finally {
-				db0.endTransaction(txn);
-			}
+		addListeners(true, true);
 
-			// actually send message
-			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
+		// make introduction
+		long time = clock.currentTimeMillis();
+		introductionManager0
+				.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
 
-			// make sure it does not arrive
-			assertFalse(listener1.requestReceived);
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// get SessionId
+		List<IntroductionMessage> list = new ArrayList<>(
+				introductionManager1.getIntroductionMessages(contactId0From1));
+		assertEquals(2, list.size());
+		assertTrue(list.get(0) instanceof IntroductionRequest);
+		IntroductionRequest msg = (IntroductionRequest) list.get(0);
+		SessionId sessionId = msg.getSessionId();
+
+		// get contact group
+		Group group =
+				introductionGroupFactory.createIntroductionGroup(contact1From0);
+
+		// create new message with same SessionId
+		BdfDictionary d = BdfDictionary.of(
+				new BdfEntry(TYPE, TYPE_REQUEST),
+				new BdfEntry(SESSION_ID, sessionId),
+				new BdfEntry(GROUP_ID, group.getId()),
+				new BdfEntry(NAME, TestUtils.getRandomString(42)),
+				new BdfEntry(PUBLIC_KEY,
+						TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH))
+		);
+
+		// reset request received state
+		listener1.requestReceived = false;
+
+		// add the message to the queue
+		MessageSender sender0 = c0.getMessageSender();
+		Transaction txn = db0.startTransaction(false);
+		try {
+			sender0.sendMessage(txn, d);
+			db0.commitTransaction(txn);
 		} finally {
-			stopLifecycles();
+			db0.endTransaction(txn);
 		}
+
+		// actually send message
+		sync0To1(1, false);
+
+		// make sure it does not arrive
+		assertFalse(listener1.requestReceived);
 	}
 
 	@Test
 	public void testIntroducerRemovedCleanup() throws Exception {
-		startLifecycles();
+		addListeners(true, true);
+
+		// make introduction
+		long time = clock.currentTimeMillis();
+		introductionManager0
+				.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// get database and local group for introducee
+		Group group1 = introductionGroupFactory.createLocalGroup();
+
+		// get local session state messages
+		Map<MessageId, Metadata> map;
+		Transaction txn = db1.startTransaction(false);
 		try {
-			getDefaultIdentities();
-			addDefaultContacts();
-			addListeners(true, true);
-			addTransportProperties();
-
-			// make introduction
-			long time = clock.currentTimeMillis();
-			Contact introducee1 = contactManager0.getContact(contactId1);
-			Contact introducee2 = contactManager0.getContact(contactId2);
-			introductionManager0
-					.makeIntroduction(introducee1, introducee2, "Hi!", time);
-
-			// sync first request message
-			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
-
-			// get database and local group for introducee
-			DatabaseComponent db1 = t1.getDatabaseComponent();
-			IntroductionGroupFactory groupFactory1 =
-					t1.getIntroductionGroupFactory();
-			Group group1 = groupFactory1.createLocalGroup();
-
-			// get local session state messages
-			Map<MessageId, Metadata> map;
-			Transaction txn = db1.startTransaction(false);
-			try {
-				map = db1.getMessageMetadata(txn, group1.getId());
-				db1.commitTransaction(txn);
-			} finally {
-				db1.endTransaction(txn);
-			}
-			// check that we have one session state
-			assertEquals(1, map.size());
+			map = db1.getMessageMetadata(txn, group1.getId());
+			db1.commitTransaction(txn);
+		} finally {
+			db1.endTransaction(txn);
+		}
+		// check that we have one session state
+		assertEquals(1, map.size());
 
-			// introducee1 removes introducer
-			contactManager1.removeContact(contactId0);
+		// introducee1 removes introducer
+		contactManager1.removeContact(contactId0From1);
 
-			// get local session state messages again
-			txn = db1.startTransaction(false);
-			try {
-				map = db1.getMessageMetadata(txn, group1.getId());
-				db1.commitTransaction(txn);
-			} finally {
-				db1.endTransaction(txn);
-			}
-			// make sure local state got deleted
-			assertEquals(0, map.size());
+		// get local session state messages again
+		txn = db1.startTransaction(false);
+		try {
+			map = db1.getMessageMetadata(txn, group1.getId());
+			db1.commitTransaction(txn);
 		} finally {
-			stopLifecycles();
+			db1.endTransaction(txn);
 		}
+		// make sure local state got deleted
+		assertEquals(0, map.size());
 	}
 
 	@Test
 	public void testIntroduceesRemovedCleanup() throws Exception {
-		startLifecycles();
+		addListeners(true, true);
+
+		// make introduction
+		long time = clock.currentTimeMillis();
+		introductionManager0
+				.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
+
+		// sync first request message
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener1.requestReceived);
+
+		// get database and local group for introducee
+		Group group1 = introductionGroupFactory.createLocalGroup();
+
+		// get local session state messages
+		Map<MessageId, Metadata> map;
+		Transaction txn = db0.startTransaction(false);
 		try {
-			getDefaultIdentities();
-			addDefaultContacts();
-			addListeners(true, true);
-			addTransportProperties();
-
-			// make introduction
-			long time = clock.currentTimeMillis();
-			Contact introducee1 = contactManager0.getContact(contactId1);
-			Contact introducee2 = contactManager0.getContact(contactId2);
-			introductionManager0
-					.makeIntroduction(introducee1, introducee2, "Hi!", time);
-
-			// sync first request message
-			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-			eventWaiter.await(TIMEOUT, 1);
-			assertTrue(listener1.requestReceived);
-
-			// get database and local group for introducee
-			DatabaseComponent db0 = t0.getDatabaseComponent();
-			IntroductionGroupFactory groupFactory0 =
-					t0.getIntroductionGroupFactory();
-			Group group1 = groupFactory0.createLocalGroup();
-
-			// get local session state messages
-			Map<MessageId, Metadata> map;
-			Transaction txn = db0.startTransaction(false);
-			try {
-				map = db0.getMessageMetadata(txn, group1.getId());
-				db0.commitTransaction(txn);
-			} finally {
-				db0.endTransaction(txn);
-			}
-			// check that we have one session state
-			assertEquals(1, map.size());
+			map = db0.getMessageMetadata(txn, group1.getId());
+			db0.commitTransaction(txn);
+		} finally {
+			db0.endTransaction(txn);
+		}
+		// check that we have one session state
+		assertEquals(1, map.size());
 
-			// introducer removes introducee1
-			contactManager0.removeContact(contactId1);
+		// introducer removes introducee1
+		contactManager0.removeContact(contactId1From0);
 
-			// get local session state messages again
-			txn = db0.startTransaction(false);
-			try {
-				map = db0.getMessageMetadata(txn, group1.getId());
-				db0.commitTransaction(txn);
-			} finally {
-				db0.endTransaction(txn);
-			}
-			// make sure local state is still there
-			assertEquals(1, map.size());
+		// get local session state messages again
+		txn = db0.startTransaction(false);
+		try {
+			map = db0.getMessageMetadata(txn, group1.getId());
+			db0.commitTransaction(txn);
+		} finally {
+			db0.endTransaction(txn);
+		}
+		// make sure local state is still there
+		assertEquals(1, map.size());
 
-			// introducer removes other introducee
-			contactManager0.removeContact(contactId2);
+		// introducer removes other introducee
+		contactManager0.removeContact(contactId2From0);
 
-			// get local session state messages again
-			txn = db0.startTransaction(false);
-			try {
-				map = db0.getMessageMetadata(txn, group1.getId());
-				db0.commitTransaction(txn);
-			} finally {
-				db0.endTransaction(txn);
-			}
-			// make sure local state is gone now
-			assertEquals(0, map.size());
+		// get local session state messages again
+		txn = db0.startTransaction(false);
+		try {
+			map = db0.getMessageMetadata(txn, group1.getId());
+			db0.commitTransaction(txn);
 		} finally {
-			stopLifecycles();
+			db0.endTransaction(txn);
 		}
+		// make sure local state is gone now
+		assertEquals(0, map.size());
 	}
 
 	private void testModifiedResponse(StateVisitor visitor)
 			throws Exception {
-		startLifecycles();
+		addListeners(true, true);
+
+		// make introduction
+		long time = clock.currentTimeMillis();
+		introductionManager0
+				.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
+
+		// sync request messages
+		sync0To1(1, true);
+		sync0To2(1, true);
+		eventWaiter.await(TIMEOUT, 2);
+
+		// sync first response
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+
+		// get response to be forwarded
+		ClientHelper ch = c0.getClientHelper(); // need 0's ClientHelper here
+		Entry<MessageId, BdfDictionary> resp =
+				getMessageFor(ch, contact2From0, TYPE_RESPONSE);
+		MessageId responseId = resp.getKey();
+		BdfDictionary response = resp.getValue();
+
+		// adapt outgoing message queue to removed message
+		Group g2 = introductionGroupFactory
+				.createIntroductionGroup(contact2From0);
+		decreaseOutgoingMessageCounter(ch, g2.getId(), 1);
+
+		// allow visitor to modify response
+		boolean earlyAbort = visitor.visit(response);
+
+		// replace original response with modified one
+		MessageSender sender0 = c0.getMessageSender();
+		Transaction txn = db0.startTransaction(false);
 		try {
-			getDefaultIdentities();
-			addDefaultContacts();
-			addListeners(true, true);
-			addTransportProperties();
-
-			// make introduction
-			long time = clock.currentTimeMillis();
-			Contact introducee1 = contactManager0.getContact(contactId1);
-			Contact introducee2 = contactManager0.getContact(contactId2);
-			introductionManager0
-					.makeIntroduction(introducee1, introducee2, "Hi!", time);
-
-			// sync request messages
-			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-			deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
-			eventWaiter.await(TIMEOUT, 2);
-
-			// sync first response
-			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
-			eventWaiter.await(TIMEOUT, 1);
-
-			// get response to be forwarded
-			Entry<MessageId, BdfDictionary> resp =
-					getMessageFor(introducee2, TYPE_RESPONSE);
-			MessageId responseId = resp.getKey();
-			BdfDictionary response = resp.getValue();
-
-			// adapt outgoing message queue to removed message
-			ClientHelper clientHelper0 = t0.getClientHelper();
-			Group g2 = introductionGroupFactory
-					.createIntroductionGroup(introducee2);
-			decreaseOutgoingMessageCounter(clientHelper0, g2.getId(), 1);
-
-			// allow visitor to modify response
-			boolean earlyAbort = visitor.visit(response);
-
-			// replace original response with modified one
-			MessageSender sender0 = t0.getMessageSender();
-			DatabaseComponent db0 = t0.getDatabaseComponent();
-			Transaction txn = db0.startTransaction(false);
-			try {
-				db0.deleteMessage(txn, responseId);
-				sender0.sendMessage(txn, response);
-				db0.commitTransaction(txn);
-			} finally {
-				db0.endTransaction(txn);
-			}
+			db0.deleteMessage(txn, responseId);
+			sender0.sendMessage(txn, response);
+			db0.commitTransaction(txn);
+		} finally {
+			db0.endTransaction(txn);
+		}
 
-			// sync second response
-			deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
-			eventWaiter.await(TIMEOUT, 1);
+		// sync second response
+		sync2To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
 
-			// sync forwarded responses to introducees
-			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-			deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
+		// sync forwarded responses to introducees
+		sync0To1(1, true);
+		sync0To2(1, true);
 
-			// sync first ACK and forward it
-			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
-			deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
+		// sync first ACK and forward it
+		sync1To0(1, true);
+		sync0To2(1, true);
 
-			// introducee2 should have detected the fake now
-			// and deleted introducee1 again
-			Collection<Contact> contacts2;
-			DatabaseComponent db2 = t2.getDatabaseComponent();
-			txn = db2.startTransaction(true);
+		// introducee2 should have detected the fake now
+		// and deleted introducee1 again
+		Collection<Contact> contacts2;
+		txn = db2.startTransaction(true);
+		try {
+			contacts2 = db2.getContacts(txn);
+			db2.commitTransaction(txn);
+		} finally {
+			db2.endTransaction(txn);
+		}
+		assertEquals(1, contacts2.size());
+
+		// sync introducee2's ack and following abort
+		sync2To0(2, true);
+
+		// ensure introducer got the abort
+		assertTrue(listener0.aborted);
+
+		// sync abort messages to introducees
+		sync0To1(2, true);
+		sync0To2(1, true);
+
+		if (earlyAbort) {
+			assertTrue(listener1.aborted);
+			assertTrue(listener2.aborted);
+		} else {
+			assertTrue(listener2.aborted);
+			// when aborted late, introducee1 keeps the contact,
+			// so introducer can not make contacts disappear by aborting
+			Collection<Contact> contacts1;
+			txn = db1.startTransaction(true);
 			try {
-				contacts2 = db2.getContacts(txn);
-				db2.commitTransaction(txn);
+				contacts1 = db1.getContacts(txn);
+				db1.commitTransaction(txn);
 			} finally {
-				db2.endTransaction(txn);
-			}
-			assertEquals(1, contacts2.size());
-
-			// sync introducee2's ack and following abort
-			deliverMessage(sync2, contactId2, sync0, contactId0, 2, "2 to 0");
-
-			// ensure introducer got the abort
-			assertTrue(listener0.aborted);
-
-			// sync abort messages to introducees
-			deliverMessage(sync0, contactId0, sync1, contactId1, 2, "0 to 1");
-			deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
-
-			if (earlyAbort) {
-				assertTrue(listener1.aborted);
-				assertTrue(listener2.aborted);
-			} else {
-				assertTrue(listener2.aborted);
-				// when aborted late, introducee1 keeps the contact,
-				// so introducer can not make contacts disappear by aborting
-				Collection<Contact> contacts1;
-				DatabaseComponent db1 = t1.getDatabaseComponent();
-				txn = db1.startTransaction(true);
-				try {
-					contacts1 = db1.getContacts(txn);
-					db1.commitTransaction(txn);
-				} finally {
-					db1.endTransaction(txn);
-				}
-				assertEquals(2, contacts1.size());
+				db1.endTransaction(txn);
 			}
-		} finally {
-			stopLifecycles();
+			assertEquals(2, contacts1.size());
 		}
 	}
 
@@ -898,16 +705,12 @@ public class IntroductionIntegrationTest extends BriarIntegrationTest {
 	public void testModifiedEphemeralPublicKeyWithFakeMac()
 			throws Exception {
 		// initialize a real introducee manager
-		MessageSender messageSender = t2.getMessageSender();
-		DatabaseComponent db = t2.getDatabaseComponent();
-		ClientHelper clientHelper = t2.getClientHelper();
-		TransportPropertyManager tpManager = t2.getTransportPropertyManager();
-		ContactManager contactManager = t2.getContactManager();
-		IdentityManager identityManager = t2.getIdentityManager();
+		MessageSender messageSender = c2.getMessageSender();
+		TransportPropertyManager tpManager = c2.getTransportPropertyManager();
 		IntroduceeManager manager2 =
-				new IntroduceeManager(messageSender, db, clientHelper, clock,
-						crypto, tpManager, authorFactory, contactManager,
-						identityManager, introductionGroupFactory);
+				new IntroduceeManager(messageSender, db2, clientHelper, clock,
+						crypto, tpManager, authorFactory, contactManager2,
+						identityManager2, introductionGroupFactory);
 
 		// create keys
 		KeyPair keyPair1 = crypto.generateSignatureKeyPair();
@@ -977,144 +780,49 @@ public class IntroductionIntegrationTest extends BriarIntegrationTest {
 		try {
 			manager2.verifySignature(state);
 			fail();
-		} catch(GeneralSecurityException e) {
+		} catch (GeneralSecurityException e) {
 			// expected
 		}
 	}
 
-	@After
-	public void tearDown() throws InterruptedException {
-		TestUtils.deleteTestDirectory(testDir);
-	}
-
-	private void startLifecycles() throws InterruptedException {
-		// Start the lifecycle manager and wait for it to finish
-		lifecycleManager0 = t0.getLifecycleManager();
-		lifecycleManager1 = t1.getLifecycleManager();
-		lifecycleManager2 = t2.getLifecycleManager();
-		lifecycleManager0.startServices(INTRODUCER);
-		lifecycleManager1.startServices(INTRODUCEE1);
-		lifecycleManager2.startServices(INTRODUCEE2);
-		lifecycleManager0.waitForStartup();
-		lifecycleManager1.waitForStartup();
-		lifecycleManager2.waitForStartup();
-	}
-
-	private void stopLifecycles() throws InterruptedException {
-		// Clean up
-		lifecycleManager0.stopServices();
-		lifecycleManager1.stopServices();
-		lifecycleManager2.stopServices();
-		lifecycleManager0.waitForShutdown();
-		lifecycleManager1.waitForShutdown();
-		lifecycleManager2.waitForShutdown();
-	}
-
 	private void addTransportProperties()
 			throws DbException, IOException, TimeoutException {
-		TransportPropertyManager tpm0 = t0.getTransportPropertyManager();
-		TransportPropertyManager tpm1 = t1.getTransportPropertyManager();
-		TransportPropertyManager tpm2 = t2.getTransportPropertyManager();
+		TransportPropertyManager tpm0 = c0.getTransportPropertyManager();
+		TransportPropertyManager tpm1 = c1.getTransportPropertyManager();
+		TransportPropertyManager tpm2 = c2.getTransportPropertyManager();
 		TransportProperties tp = new TransportProperties(
 				Collections.singletonMap("key", "value"));
 
 		tpm0.mergeLocalProperties(TRANSPORT_ID, tp);
-		deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
-		deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
+		sync0To1(1, true);
+		sync0To2(1, true);
 
 		tpm1.mergeLocalProperties(TRANSPORT_ID, tp);
-		deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
+		sync1To0(1, true);
 
 		tpm2.mergeLocalProperties(TRANSPORT_ID, tp);
-		deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
-	}
-
-	private void addListeners(boolean accept1, boolean accept2) {
-		// listen to events
-		listener0 = new IntroducerListener();
-		t0.getEventBus().addListener(listener0);
-		listener1 = new IntroduceeListener(1, accept1);
-		t1.getEventBus().addListener(listener1);
-		listener2 = new IntroduceeListener(2, accept2);
-		t2.getEventBus().addListener(listener2);
-	}
-
-	private void getDefaultIdentities() throws DbException {
-		author0 = identityManager0.getLocalAuthor();
-		author1 = identityManager1.getLocalAuthor();
-		author2 = identityManager2.getLocalAuthor();
-
-	}
-
-	private void addDefaultContacts() throws DbException {
-		// Add introducees as contacts
-		contactId1 = contactManager0.addContact(author1,
-				author0.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		contactId2 = contactManager0.addContact(author2,
-				author0.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		// Add introducer back
-		contactId0 = contactManager1.addContact(author0,
-				author1.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		ContactId contactId02 = contactManager2.addContact(author0,
-				author2.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		assertTrue(contactId0.equals(contactId02));
-	}
-
-	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
-			SyncSessionFactory toSync, ContactId toId, String debug)
-			throws IOException, TimeoutException {
-		deliverMessage(fromSync, fromId, toSync, toId, 1, debug);
-	}
-
-	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
-			SyncSessionFactory toSync, ContactId toId)
-			throws IOException, TimeoutException {
-		deliverMessage(fromSync, fromId, toSync, toId, 1, null);
-	}
-
-	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
-			SyncSessionFactory toSync, ContactId toId, int num,
-			@Nullable String debug)
-			throws IOException, TimeoutException {
-
-		if (debug != null) LOG.info("TEST: Sending message from " + debug);
-
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		// Create an outgoing sync session
-		SyncSession sessionFrom =
-				fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
-		// Write whatever needs to be written
-		sessionFrom.run();
-		out.close();
-
-		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-		// Create an incoming sync session
-		SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
-		// Read whatever needs to be read
-		sessionTo.run();
-		in.close();
-
-		// wait for [num] message(s) to actually arrive
-		msgWaiter.await(TIMEOUT, num);
+		sync2To0(1, true);
 	}
 
 	private void assertDefaultUiMessages() throws DbException {
 		assertEquals(2, introductionManager0.getIntroductionMessages(
-				contactId1).size());
+				contactId1From0).size());
 		assertEquals(2, introductionManager0.getIntroductionMessages(
-				contactId2).size());
+				contactId2From0).size());
 		assertEquals(2, introductionManager1.getIntroductionMessages(
-				contactId0).size());
+				contactId0From1).size());
 		assertEquals(2, introductionManager2.getIntroductionMessages(
-				contactId0).size());
+				contactId0From2).size());
+	}
+
+	private void addListeners(boolean accept1, boolean accept2) {
+		// listen to events
+		listener0 = new IntroducerListener();
+		c0.getEventBus().addListener(listener0);
+		listener1 = new IntroduceeListener(1, accept1);
+		c1.getEventBus().addListener(listener1);
+		listener2 = new IntroduceeListener(2, accept2);
+		c2.getEventBus().addListener(listener2);
 	}
 
 	private class IntroduceeListener implements EventListener {
@@ -1135,15 +843,7 @@ public class IntroductionIntegrationTest extends BriarIntegrationTest {
 
 		@Override
 		public void eventOccurred(Event e) {
-			if (e instanceof MessageStateChangedEvent) {
-				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
-				State s = event.getState();
-				if ((s == DELIVERED || s == INVALID) && !event.isLocal()) {
-					LOG.info("TEST: Introducee" + introducee +
-							" received message");
-					msgWaiter.resume();
-				}
-			} else if (e instanceof IntroductionRequestReceivedEvent) {
+			if (e instanceof IntroductionRequestReceivedEvent) {
 				IntroductionRequestReceivedEvent introEvent =
 						((IntroductionRequestReceivedEvent) e);
 				requestReceived = true;
@@ -1181,7 +881,8 @@ public class IntroductionIntegrationTest extends BriarIntegrationTest {
 			} else if (e instanceof IntroductionSucceededEvent) {
 				succeeded = true;
 				Contact contact = ((IntroductionSucceededEvent) e).getContact();
-				eventWaiter.assertFalse(contact.getId().equals(contactId0));
+				eventWaiter
+						.assertFalse(contact.getId().equals(contactId0From1));
 				eventWaiter.assertTrue(contact.isActive());
 				eventWaiter.resume();
 			} else if (e instanceof IntroductionAbortedEvent) {
@@ -1199,18 +900,13 @@ public class IntroductionIntegrationTest extends BriarIntegrationTest {
 
 		@Override
 		public void eventOccurred(Event e) {
-			if (e instanceof MessageStateChangedEvent) {
-				MessageStateChangedEvent event = (MessageStateChangedEvent) e;
-				if (event.getState() == DELIVERED && !event.isLocal()) {
-					LOG.info("TEST: Introducer received message");
-					msgWaiter.resume();
-				}
-			} else if (e instanceof IntroductionResponseReceivedEvent) {
+			if (e instanceof IntroductionResponseReceivedEvent) {
 				ContactId c =
-						((IntroductionResponseReceivedEvent) e).getContactId();
-				if (c.equals(contactId1)) {
+						((IntroductionResponseReceivedEvent) e)
+								.getContactId();
+				if (c.equals(contactId1From0)) {
 					response1Received = true;
-				} else if (c.equals(contactId2)) {
+				} else if (c.equals(contactId2From0)) {
 					response2Received = true;
 				}
 				eventWaiter.resume();
@@ -1219,26 +915,26 @@ public class IntroductionIntegrationTest extends BriarIntegrationTest {
 				eventWaiter.resume();
 			}
 		}
+
 	}
 
-	private void decreaseOutgoingMessageCounter(ClientHelper clientHelper,
-			GroupId g, int num) throws FormatException, DbException {
-		BdfDictionary gD = clientHelper.getGroupMetadataAsDictionary(g);
+	private void decreaseOutgoingMessageCounter(ClientHelper ch, GroupId g,
+			int num) throws FormatException, DbException {
+		BdfDictionary gD = ch.getGroupMetadataAsDictionary(g);
 		LOG.warning(gD.toString());
 		BdfDictionary queue = gD.getDictionary(QUEUE_STATE_KEY);
 		queue.put("nextOut", queue.getLong("nextOut") - num);
 		gD.put(QUEUE_STATE_KEY, queue);
-		clientHelper.mergeGroupMetadata(g, gD);
+		ch.mergeGroupMetadata(g, gD);
 	}
 
-	private Entry<MessageId, BdfDictionary> getMessageFor(Contact contact,
-			long type) throws FormatException, DbException {
+	private Entry<MessageId, BdfDictionary> getMessageFor(ClientHelper ch,
+			Contact contact, long type) throws FormatException, DbException {
 		Entry<MessageId, BdfDictionary> response = null;
 		Group g = introductionGroupFactory
 				.createIntroductionGroup(contact);
-		ClientHelper clientHelper0 = t0.getClientHelper();
 		Map<MessageId, BdfDictionary> map =
-				clientHelper0.getMessageMetadataAsDictionary(g.getId());
+				ch.getMessageMetadataAsDictionary(g.getId());
 		for (Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
 			if (entry.getValue().getLong(TYPE) == type) {
 				response = entry;
@@ -1248,17 +944,4 @@ public class IntroductionIntegrationTest extends BriarIntegrationTest {
 		return response;
 	}
 
-	private void injectEagerSingletons(
-			IntroductionIntegrationTestComponent component) {
-
-		component.inject(new LifecycleModule.EagerSingletons());
-		component.inject(new LifecycleModule.EagerSingletons());
-		component.inject(new IntroductionModule.EagerSingletons());
-		component.inject(new CryptoModule.EagerSingletons());
-		component.inject(new ContactModule.EagerSingletons());
-		component.inject(new TransportModule.EagerSingletons());
-		component.inject(new SyncModule.EagerSingletons());
-		component.inject(new SystemModule.EagerSingletons());
-		component.inject(new PropertiesModule.EagerSingletons());
-	}
 }
diff --git a/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTestComponent.java
deleted file mode 100644
index 1741364824258fd826122d5814cb27bd133144d6..0000000000000000000000000000000000000000
--- a/briar-android-tests/src/test/java/org/briarproject/introduction/IntroductionIntegrationTestComponent.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package org.briarproject.introduction;
-
-import org.briarproject.TestDatabaseModule;
-import org.briarproject.TestPluginsModule;
-import org.briarproject.TestSeedProviderModule;
-import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.clients.MessageTracker;
-import org.briarproject.api.contact.ContactManager;
-import org.briarproject.api.db.DatabaseComponent;
-import org.briarproject.api.event.EventBus;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.introduction.IntroductionManager;
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.properties.TransportPropertyManager;
-import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.clients.ClientsModule;
-import org.briarproject.contact.ContactModule;
-import org.briarproject.crypto.CryptoModule;
-import org.briarproject.data.DataModule;
-import org.briarproject.db.DatabaseModule;
-import org.briarproject.event.EventModule;
-import org.briarproject.identity.IdentityModule;
-import org.briarproject.lifecycle.LifecycleModule;
-import org.briarproject.messaging.MessagingModule;
-import org.briarproject.properties.PropertiesModule;
-import org.briarproject.sync.SyncModule;
-import org.briarproject.system.SystemModule;
-import org.briarproject.transport.TransportModule;
-
-import javax.inject.Singleton;
-
-import dagger.Component;
-
-@Singleton
-@Component(modules = {
-		TestDatabaseModule.class,
-		TestPluginsModule.class,
-		TestSeedProviderModule.class,
-		LifecycleModule.class,
-		IntroductionModule.class,
-		DatabaseModule.class,
-		CryptoModule.class,
-		EventModule.class,
-		ContactModule.class,
-		IdentityModule.class,
-		TransportModule.class,
-		ClientsModule.class,
-		SyncModule.class,
-		SystemModule.class,
-		DataModule.class,
-		PropertiesModule.class,
-		MessagingModule.class
-})
-interface IntroductionIntegrationTestComponent {
-
-	void inject(IntroductionIntegrationTest testCase);
-
-	void inject(ContactModule.EagerSingletons init);
-
-	void inject(CryptoModule.EagerSingletons init);
-
-	void inject(IntroductionModule.EagerSingletons init);
-
-	void inject(LifecycleModule.EagerSingletons init);
-
-	void inject(PropertiesModule.EagerSingletons init);
-
-	void inject(SyncModule.EagerSingletons init);
-
-	void inject(SystemModule.EagerSingletons init);
-
-	void inject(TransportModule.EagerSingletons init);
-
-	LifecycleManager getLifecycleManager();
-
-	EventBus getEventBus();
-
-	IdentityManager getIdentityManager();
-
-	ContactManager getContactManager();
-
-	IntroductionManager getIntroductionManager();
-
-	TransportPropertyManager getTransportPropertyManager();
-
-	MessageTracker getMessageTracker();
-
-	SyncSessionFactory getSyncSessionFactory();
-
-	/* the following methods are only needed to manually construct messages */
-
-	DatabaseComponent getDatabaseComponent();
-
-	ClientHelper getClientHelper();
-
-	MessageSender getMessageSender();
-
-	IntroductionGroupFactory getIntroductionGroupFactory();
-}
diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java b/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java
index 12cc01323d3cf012f617a03981525ac4a620612f..a7e7e3db73dc4c56373b620665340c8216889ce7 100644
--- a/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java
+++ b/briar-api/src/org/briarproject/api/introduction/IntroductionManager.java
@@ -8,6 +8,7 @@ import org.briarproject.api.db.DbException;
 import org.briarproject.api.messaging.ConversationManager.ConversationClient;
 import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.sync.ClientId;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 
@@ -20,7 +21,7 @@ public interface IntroductionManager extends ConversationClient {
 	/**
 	 * sends two initial introduction messages
 	 */
-	void makeIntroduction(Contact c1, Contact c2, String msg,
+	void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
 			final long timestamp)
 			throws DbException, FormatException;
 
diff --git a/briar-api/src/org/briarproject/api/sharing/SharingManager.java b/briar-api/src/org/briarproject/api/sharing/SharingManager.java
index 66b6ef82f6c275763d6f8a9e5c2720836d0c4b93..783e3ba3eba6c8c072516d9f92fe62a6f3d162f6 100644
--- a/briar-api/src/org/briarproject/api/sharing/SharingManager.java
+++ b/briar-api/src/org/briarproject/api/sharing/SharingManager.java
@@ -7,6 +7,7 @@ import org.briarproject.api.db.DbException;
 import org.briarproject.api.messaging.ConversationManager.ConversationClient;
 import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.sync.GroupId;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 
@@ -19,7 +20,7 @@ public interface SharingManager<S extends Shareable>
 	 * and sends an optional message along with it.
 	 */
 	void sendInvitation(GroupId groupId, ContactId contactId,
-			String message) throws DbException;
+			@Nullable String message) throws DbException;
 
 	/**
 	 * Responds to a pending group invitation
diff --git a/briar-tests/src/org/briarproject/BriarTestCase.java b/briar-tests/src/org/briarproject/BriarTestCase.java
index e847c546bfaf110fb871369c0764228fe8349ab0..3bad5d516477e67502ab55c808ce43eda520a67d 100644
--- a/briar-tests/src/org/briarproject/BriarTestCase.java
+++ b/briar-tests/src/org/briarproject/BriarTestCase.java
@@ -1,7 +1,12 @@
 package org.briarproject;
 
+import org.briarproject.api.clients.MessageTracker;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.GroupId;
+
 import java.lang.Thread.UncaughtExceptionHandler;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 public abstract class BriarTestCase {
@@ -16,4 +21,22 @@ public abstract class BriarTestCase {
 		};
 		Thread.setDefaultUncaughtExceptionHandler(fail);
 	}
+
+	protected static void assertGroupCount(MessageTracker tracker, GroupId g,
+			long msgCount, long unreadCount, long latestMsg)
+			throws DbException {
+
+		MessageTracker.GroupCount groupCount = tracker.getGroupCount(g);
+		assertEquals(msgCount, groupCount.getMsgCount());
+		assertEquals(unreadCount, groupCount.getUnreadCount());
+		assertEquals(latestMsg, groupCount.getLatestMsgTime());
+	}
+
+	protected static void assertGroupCount(MessageTracker tracker, GroupId g,
+			long msgCount, long unreadCount) throws	DbException {
+
+		MessageTracker.GroupCount c1 = tracker.getGroupCount(g);
+		assertEquals(msgCount, c1.getMsgCount());
+		assertEquals(unreadCount, c1.getUnreadCount());
+	}
 }