diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index b72849d6f9914e3910ab20d41a54167cadeff38b..f646a9e4120ffbdfd4c86532c6cc3dac122797ff 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -21,7 +21,6 @@ public interface DatabaseComponent {
 	static final long MAX_BYTES_BETWEEN_SPACE_CHECKS = 5L * MEGABYTES;
 	static final long MAX_MS_BETWEEN_SPACE_CHECKS = 60L * 1000L; // 1 min
 	static final long BYTES_PER_SWEEP = 5L * MEGABYTES;
-	static final int RETRANSMIT_THRESHOLD = 3;
 
 	/**
 	 * Opens the database.
diff --git a/api/net/sf/briar/api/db/DbException.java b/api/net/sf/briar/api/db/DbException.java
index 1061f76e4e9b156c0010173f6bd41eb6fb89aebe..3ad5030a9e522af3daf5b9773cc565c62f926cf0 100644
--- a/api/net/sf/briar/api/db/DbException.java
+++ b/api/net/sf/briar/api/db/DbException.java
@@ -4,6 +4,10 @@ public class DbException extends Exception {
 
 	private static final long serialVersionUID = 3706581789209939441L;
 
+	public DbException() {
+		super();
+	}
+
 	public DbException(Throwable t) {
 		super(t);
 	}
diff --git a/api/net/sf/briar/api/db/NoSuchContactException.java b/api/net/sf/briar/api/db/NoSuchContactException.java
new file mode 100644
index 0000000000000000000000000000000000000000..72b735493fc7020f3128d295ebdb79af04735ab8
--- /dev/null
+++ b/api/net/sf/briar/api/db/NoSuchContactException.java
@@ -0,0 +1,14 @@
+package net.sf.briar.api.db;
+
+/**
+ * Thrown when a database operation is attempted for a contact that is not in
+ * the database.
+ */
+public class NoSuchContactException extends DbException {
+
+	private static final long serialVersionUID = -7048538231308207386L;
+
+	public NoSuchContactException() {
+		super();
+	}
+}
diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index 9965e950d0c118205da151a839319430a5ed0bca..9246874b05aa801263482f43c8770a83d32c223f 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -32,6 +32,16 @@ import net.sf.briar.api.protocol.MessageId;
  */
 interface Database<T> {
 
+	/**
+	 * A batch sent to a contact is considered lost when this many bundles have
+	 * been received from the contact since the batch was sent.
+	 * <p>
+	 * FIXME: Come up with a better retransmission scheme. This scheme doesn't
+	 * cope well with transports that have high latency but send bundles
+	 * frequently.
+	 */
+	static final int RETRANSMIT_THRESHOLD = 3;
+
 	/**
 	 * Opens the database.
 	 * @param resume True to reopen an existing database, false to create a
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index efb6c67ee74b5c73bdb6538a3a745bd895344fba..1270687995947f2b6d2e9e1b46405f0b170b14cb 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -18,7 +18,6 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.sf.briar.api.db.ContactId;
-import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.db.Status;
@@ -527,7 +526,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			rs.close();
 			ps.close();
 			Set<BatchId> lost;
-			if(received == DatabaseComponent.RETRANSMIT_THRESHOLD) {
+			if(received == RETRANSMIT_THRESHOLD) {
 				// Expire batches related to the oldest received bundle
 				assert oldestBundle != null;
 				lost = findLostBatches(txn, c, oldestBundle);
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index c5d207b153b27a63b77b97f17d6279d8a23c6495..921e7fb026082199f7eb2f6b663e2885a9d7436b 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -7,8 +7,9 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.ContactId;
+import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
@@ -120,7 +121,10 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							// unsubscribed from the group
 							if(db.containsSubscription(txn, m.getGroup())) {
 								boolean added = storeMessage(txn, m, null);
-								assert added;
+								if(!added) {
+									if(LOG.isLoggable(Level.FINE))
+										LOG.fine("Duplicate local message");
+								}
 							} else {
 								if(LOG.isLoggable(Level.FINE))
 									LOG.fine("Not subscribed");
@@ -177,7 +181,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		// Ack all batches received from c
 		contactLock.readLock().lock();
 		try {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			messageStatusLock.writeLock().lock();
 			try {
 				Txn txn = db.startTransaction();
@@ -203,7 +207,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		// Add a list of subscriptions
 		contactLock.readLock().lock();
 		try {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			subscriptionLock.readLock().lock();
 			try {
 				Txn txn = db.startTransaction();
@@ -246,7 +250,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 	private Batch fillBatch(ContactId c, long capacity) throws DbException {
 		contactLock.readLock().lock();
 		try {
-			if(!containsContact(c)) return null;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			messageLock.readLock().lock();
 			try {
 				Set<MessageId> sent;
@@ -344,7 +348,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		// Mark all messages in acked batches as seen
 		contactLock.readLock().lock();
 		try {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			messageLock.readLock().lock();
 			try {
 				messageStatusLock.writeLock().lock();
@@ -375,7 +379,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		// Update the contact's subscriptions
 		contactLock.readLock().lock();
 		try {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			messageStatusLock.writeLock().lock();
 			try {
 				Txn txn = db.startTransaction();
@@ -406,7 +410,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 			waitForPermissionToWrite();
 			contactLock.readLock().lock();
 			try {
-				if(!containsContact(c)) return;
+				if(!containsContact(c)) throw new NoSuchContactException();
 				messageLock.writeLock().lock();
 				try {
 					messageStatusLock.writeLock().lock();
@@ -451,7 +455,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		Set<BatchId> lost;
 		contactLock.readLock().lock();
 		try {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			messageLock.readLock().lock();
 			try {
 				messageStatusLock.writeLock().lock();
@@ -476,7 +480,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		for(BatchId batch : lost) {
 			contactLock.readLock().lock();
 			try {
-				if(!containsContact(c)) return;
+				if(!containsContact(c)) throw new NoSuchContactException();
 				messageLock.readLock().lock();
 				try {
 					messageStatusLock.writeLock().lock();
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index 4ed4f309cb92d2e99a818a25c998c669bb4b0d02..959b3be61d059a5d391143bc83510145bcb30571 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -8,6 +8,7 @@ import java.util.logging.Logger;
 
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.ContactId;
+import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
@@ -89,7 +90,10 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							// unsubscribed from the group
 							if(db.containsSubscription(txn, m.getGroup())) {
 								boolean added = storeMessage(txn, m, null);
-								assert added;
+								if(!added) {
+									if(LOG.isLoggable(Level.FINE))
+										LOG.fine("Duplicate local message");
+								}
 							} else {
 								if(LOG.isLoggable(Level.FINE))
 									LOG.fine("Not subscribed");
@@ -128,7 +132,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c);
 		// Ack all batches received from c
 		synchronized(contactLock) {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			synchronized(messageStatusLock) {
 				Txn txn = db.startTransaction();
 				try {
@@ -148,7 +152,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 		// Add a list of subscriptions
 		synchronized(contactLock) {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			synchronized(subscriptionLock) {
 				Txn txn = db.startTransaction();
 				try {
@@ -185,7 +189,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 
 	private Batch fillBatch(ContactId c, long capacity) throws DbException {
 		synchronized(contactLock) {
-			if(!containsContact(c)) return null;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			synchronized(messageLock) {
 				synchronized(messageStatusLock) {
 					Txn txn = db.startTransaction();
@@ -254,7 +258,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 					+ b.getSize() + " bytes");
 		// Mark all messages in acked batches as seen
 		synchronized(contactLock) {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			synchronized(messageLock) {
 				synchronized(messageStatusLock) {
 					int acks = 0;
@@ -276,7 +280,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 		// Update the contact's subscriptions
 		synchronized(contactLock) {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			synchronized(messageStatusLock) {
 				Txn txn = db.startTransaction();
 				try {
@@ -301,7 +305,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 			batches++;
 			waitForPermissionToWrite();
 			synchronized(contactLock) {
-				if(!containsContact(c)) return;
+				if(!containsContact(c)) throw new NoSuchContactException();
 				synchronized(messageLock) {
 					synchronized(messageStatusLock) {
 						synchronized(subscriptionLock) {
@@ -334,7 +338,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		// Find any lost batches that need to be retransmitted
 		Set<BatchId> lost;
 		synchronized(contactLock) {
-			if(!containsContact(c)) return;
+			if(!containsContact(c)) throw new NoSuchContactException();
 			synchronized(messageLock) {
 				synchronized(messageStatusLock) {
 					Txn txn = db.startTransaction();
@@ -350,7 +354,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 		for(BatchId batch : lost) {
 			synchronized(contactLock) {
-				if(!containsContact(c)) return;
+				if(!containsContact(c)) throw new NoSuchContactException();
 				synchronized(messageLock) {
 					synchronized(messageStatusLock) {
 						Txn txn = db.startTransaction();
diff --git a/test/build.xml b/test/build.xml
index 0db40f821b6db820a2280f3f92867b3a95faef4e..4b864204d2861cc69615f3892c7066a0b7929980 100644
--- a/test/build.xml
+++ b/test/build.xml
@@ -15,6 +15,8 @@
 			</classpath>
 			<test name='net.sf.briar.db.DatabaseCleanerImplTest'/>
 			<test name='net.sf.briar.db.H2DatabaseTest'/>
+			<test name='net.sf.briar.db.ReadWriteLockDatabaseComponentTest'/>
+			<test name='net.sf.briar.db.SynchronizedDatabaseComponentTest'/>
 			<test name='net.sf.briar.i18n.FontManagerTest'/>
 			<test name='net.sf.briar.i18n.I18nTest'/>
 			<test name='net.sf.briar.invitation.InvitationWorkerTest'/>
diff --git a/test/net/sf/briar/TestUtils.java b/test/net/sf/briar/TestUtils.java
index 552fbd427e7ab0fd6a0c82a7bb494f6063b0c48d..6046f6544390b2d0a09da32779df1831203f87de 100644
--- a/test/net/sf/briar/TestUtils.java
+++ b/test/net/sf/briar/TestUtils.java
@@ -4,12 +4,16 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.util.Random;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import net.sf.briar.api.protocol.UniqueId;
+
 public class TestUtils {
 
 	private static final AtomicInteger nextTestDir =
 		new AtomicInteger((int) (Math.random() * 1000 * 1000));
+	private static final Random random = new Random();
 
 	public static void delete(File f) {
 		if(f.isDirectory()) for(File child : f.listFiles()) delete(child);
@@ -42,4 +46,10 @@ public class TestUtils {
 		if(bin.exists() && bin.isDirectory()) return bin;
 		throw new RuntimeException("Could not find build directory");
 	}
+
+	public static byte[] getRandomId() {
+		byte[] b = new byte[UniqueId.LENGTH];
+		random.nextBytes(b);
+		return b;
+	}
 }
diff --git a/test/net/sf/briar/db/DatabaseComponentImplTest.java b/test/net/sf/briar/db/DatabaseComponentImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e713c5ebad36f772b2600748630e6e62e434f001
--- /dev/null
+++ b/test/net/sf/briar/db/DatabaseComponentImplTest.java
@@ -0,0 +1,140 @@
+package net.sf.briar.db;
+
+import static net.sf.briar.api.db.DatabaseComponent.BYTES_PER_SWEEP;
+import static net.sf.briar.api.db.DatabaseComponent.MIN_FREE_SPACE;
+
+import java.util.Collections;
+
+import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.protocol.Batch;
+import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.db.DatabaseCleaner.Callback;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.junit.Test;
+
+import com.google.inject.Provider;
+
+/**
+ * Tests that use the DatabaseCleaner.Callback interface of
+ * DatabaseComponentImpl.
+ */
+public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
+
+	protected abstract <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
+			Database<T> database, DatabaseCleaner cleaner,
+			Provider<Batch> batchProvider);
+
+	@Test
+	public void testNotCleanedIfEnoughFreeSpace() throws DbException {
+		Mockery context = new Mockery();
+		@SuppressWarnings("unchecked")
+		final Database<Object> database = context.mock(Database.class);
+		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		@SuppressWarnings("unchecked")
+		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		context.checking(new Expectations() {{
+			oneOf(database).getFreeSpace();
+			will(returnValue(MIN_FREE_SPACE));
+		}});
+		Callback db = createDatabaseComponentImpl(database, cleaner,
+				batchProvider);
+
+		db.checkFreeSpaceAndClean();
+
+		context.assertIsSatisfied();
+	}
+
+	@Test
+	public void testCleanedIfNotEnoughFreeSpace() throws DbException {
+		Mockery context = new Mockery();
+		@SuppressWarnings("unchecked")
+		final Database<Object> database = context.mock(Database.class);
+		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		@SuppressWarnings("unchecked")
+		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		context.checking(new Expectations() {{
+			oneOf(database).getFreeSpace();
+			will(returnValue(MIN_FREE_SPACE - 1));
+			oneOf(database).startTransaction();
+			will(returnValue(txn));
+			oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
+			will(returnValue(Collections.emptySet()));
+			oneOf(database).commitTransaction(txn);
+			// As if by magic, some free space has appeared
+			oneOf(database).getFreeSpace();
+			will(returnValue(MIN_FREE_SPACE));
+		}});
+		Callback db = createDatabaseComponentImpl(database, cleaner,
+				batchProvider);
+
+		db.checkFreeSpaceAndClean();
+
+		context.assertIsSatisfied();
+	}
+
+	@Test
+	public void testExpiringUnsendableMessageDoesNotTriggerBackwardInclusion()
+	throws DbException {
+		Mockery context = new Mockery();
+		@SuppressWarnings("unchecked")
+		final Database<Object> database = context.mock(Database.class);
+		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		@SuppressWarnings("unchecked")
+		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		context.checking(new Expectations() {{
+			oneOf(database).getFreeSpace();
+			will(returnValue(MIN_FREE_SPACE - 1));
+			oneOf(database).startTransaction();
+			will(returnValue(txn));
+			oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
+			will(returnValue(Collections.singleton(messageId)));
+			oneOf(database).getSendability(txn, messageId);
+			will(returnValue(0));
+			oneOf(database).removeMessage(txn, messageId);
+			oneOf(database).commitTransaction(txn);
+			oneOf(database).getFreeSpace();
+			will(returnValue(MIN_FREE_SPACE));
+		}});
+		Callback db = createDatabaseComponentImpl(database, cleaner,
+				batchProvider);
+
+		db.checkFreeSpaceAndClean();
+
+		context.assertIsSatisfied();
+	}
+
+	@Test
+	public void testExpiringSendableMessageTriggersBackwardInclusion()
+	throws DbException {
+		Mockery context = new Mockery();
+		@SuppressWarnings("unchecked")
+		final Database<Object> database = context.mock(Database.class);
+		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		@SuppressWarnings("unchecked")
+		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		context.checking(new Expectations() {{
+			oneOf(database).getFreeSpace();
+			will(returnValue(MIN_FREE_SPACE - 1));
+			oneOf(database).startTransaction();
+			will(returnValue(txn));
+			oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
+			will(returnValue(Collections.singleton(messageId)));
+			oneOf(database).getSendability(txn, messageId);
+			will(returnValue(1));
+			oneOf(database).getParent(txn, messageId);
+			will(returnValue(MessageId.NONE));
+			oneOf(database).removeMessage(txn, messageId);
+			oneOf(database).commitTransaction(txn);
+			oneOf(database).getFreeSpace();
+			will(returnValue(MIN_FREE_SPACE));
+		}});
+		Callback db = createDatabaseComponentImpl(database, cleaner,
+				batchProvider);
+
+		db.checkFreeSpaceAndClean();
+
+		context.assertIsSatisfied();
+	}
+}
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 30054c911359ee79a363cb34d80396bdaff565e5..ea3b011bf54b5f167682abb824bdd8d4a90eb8b6 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -1,8 +1,6 @@
 package net.sf.briar.db;
 
-import java.io.File;
 import java.util.Collections;
-import java.util.Random;
 import java.util.Set;
 
 import junit.framework.TestCase;
@@ -10,10 +8,12 @@ import net.sf.briar.TestUtils;
 import net.sf.briar.api.db.ContactId;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.db.Rating;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
+import net.sf.briar.api.protocol.Bundle;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
@@ -21,37 +21,32 @@ import net.sf.briar.protocol.MessageImpl;
 
 import org.jmock.Expectations;
 import org.jmock.Mockery;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import com.google.inject.Provider;
 
 public abstract class DatabaseComponentTest extends TestCase {
 
-	private final File testDir = TestUtils.getTestDirectory();
-	private final Random random = new Random();
-	private final AuthorId authorId;
-	private final ContactId contactId;
-	private final GroupId groupId;
-	private final MessageId messageId, parentId;
+	protected final Object txn = new Object();
+	protected final AuthorId authorId;
+	protected final ContactId contactId;
+	protected final GroupId groupId;
+	protected final MessageId messageId, parentId;
 	private final long timestamp;
 	private final int size;
 	private final byte[] body;
 	private final Message message;
-	private final Object txn = new Object();
 
 	public DatabaseComponentTest() {
 		super();
-		authorId = new AuthorId(getRandomId());
+		authorId = new AuthorId(TestUtils.getRandomId());
 		contactId = new ContactId(123);
-		groupId = new GroupId(getRandomId());
-		messageId = new MessageId(getRandomId());
-		parentId = new MessageId(getRandomId());
+		groupId = new GroupId(TestUtils.getRandomId());
+		messageId = new MessageId(TestUtils.getRandomId());
+		parentId = new MessageId(TestUtils.getRandomId());
 		timestamp = System.currentTimeMillis();
 		size = 1234;
 		body = new byte[size];
-		random.nextBytes(body);
 		message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId,
 				timestamp, body);
 	}
@@ -60,11 +55,6 @@ public abstract class DatabaseComponentTest extends TestCase {
 			Database<T> database, DatabaseCleaner cleaner,
 			Provider<Batch> batchProvider);
 
-	@Before
-	public void setUp() {
-		testDir.mkdirs();
-	}
-
 	@Test
 	public void testSimpleCalls() throws DbException {
 		final Set<GroupId> subs = Collections.singleton(groupId);
@@ -137,8 +127,6 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Provider<Batch> batchProvider = context.mock(Provider.class);
 		context.checking(new Expectations() {{
-			oneOf(database).open(false);
-			oneOf(cleaner).startCleaning();
 			// setRating(Rating.GOOD)
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -153,15 +141,11 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).getParent(txn, messageId);
 			will(returnValue(MessageId.NONE));
 			oneOf(database).commitTransaction(txn);
-			oneOf(cleaner).stopCleaning();
-			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				batchProvider);
 
-		db.open(false);
 		db.setRating(authorId, Rating.GOOD);
-		db.close();
 
 		context.assertIsSatisfied();
 	}
@@ -176,8 +160,6 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Provider<Batch> batchProvider = context.mock(Provider.class);
 		context.checking(new Expectations() {{
-			oneOf(database).open(false);
-			oneOf(cleaner).startCleaning();
 			// setRating(Rating.GOOD)
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -195,22 +177,18 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).containsMessage(txn, parentId);
 			will(returnValue(false));
 			oneOf(database).commitTransaction(txn);
-			oneOf(cleaner).stopCleaning();
-			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				batchProvider);
 
-		db.open(false);
 		db.setRating(authorId, Rating.GOOD);
-		db.close();
 
 		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testChangingGroupsStopsBackwardInclusion() throws DbException {
-		final GroupId groupId1 = new GroupId(getRandomId());
+		final GroupId groupId1 = new GroupId(TestUtils.getRandomId());
 		final Set<MessageId> messages = Collections.singleton(messageId);
 		Mockery context = new Mockery();
 		@SuppressWarnings("unchecked")
@@ -219,8 +197,6 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Provider<Batch> batchProvider = context.mock(Provider.class);
 		context.checking(new Expectations() {{
-			oneOf(database).open(false);
-			oneOf(cleaner).startCleaning();
 			// setRating(Rating.GOOD)
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -242,15 +218,11 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).getGroup(txn, parentId);
 			will(returnValue(groupId1));
 			oneOf(database).commitTransaction(txn);
-			oneOf(cleaner).stopCleaning();
-			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				batchProvider);
 
-		db.open(false);
 		db.setRating(authorId, Rating.GOOD);
-		db.close();
 
 		context.assertIsSatisfied();
 	}
@@ -266,8 +238,6 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Provider<Batch> batchProvider = context.mock(Provider.class);
 		context.checking(new Expectations() {{
-			oneOf(database).open(false);
-			oneOf(cleaner).startCleaning();
 			// setRating(Rating.GOOD)
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -292,15 +262,11 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(1));
 			oneOf(database).setSendability(txn, parentId, 2);
 			oneOf(database).commitTransaction(txn);
-			oneOf(cleaner).stopCleaning();
-			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				batchProvider);
 
-		db.open(false);
 		db.setRating(authorId, Rating.GOOD);
-		db.close();
 
 		context.assertIsSatisfied();
 	}
@@ -316,8 +282,6 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Provider<Batch> batchProvider = context.mock(Provider.class);
 		context.checking(new Expectations() {{
-			oneOf(database).open(false);
-			oneOf(cleaner).startCleaning();
 			// setRating(Rating.GOOD)
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -344,15 +308,11 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).getParent(txn, parentId);
 			will(returnValue(MessageId.NONE));
 			oneOf(database).commitTransaction(txn);
-			oneOf(cleaner).stopCleaning();
-			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				batchProvider);
 
-		db.open(false);
 		db.setRating(authorId, Rating.GOOD);
-		db.close();
 
 		context.assertIsSatisfied();
 	}
@@ -367,23 +327,17 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Provider<Batch> batchProvider = context.mock(Provider.class);
 		context.checking(new Expectations() {{
-			oneOf(database).open(false);
-			oneOf(cleaner).startCleaning();
 			// addLocallyGeneratedMessage(message)
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
 			oneOf(database).containsSubscription(txn, groupId);
 			will(returnValue(false));
 			oneOf(database).commitTransaction(txn);
-			oneOf(cleaner).stopCleaning();
-			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				batchProvider);
 
-		db.open(false);
 		db.addLocallyGeneratedMessage(message);
-		db.close();
 
 		context.assertIsSatisfied();
 	}
@@ -397,8 +351,6 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Provider<Batch> batchProvider = context.mock(Provider.class);
 		context.checking(new Expectations() {{
-			oneOf(database).open(false);
-			oneOf(cleaner).startCleaning();
 			// addLocallyGeneratedMessage(message)
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -407,15 +359,11 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).addMessage(txn, message);
 			will(returnValue(false));
 			oneOf(database).commitTransaction(txn);
-			oneOf(cleaner).stopCleaning();
-			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				batchProvider);
 
-		db.open(false);
 		db.addLocallyGeneratedMessage(message);
-		db.close();
 
 		context.assertIsSatisfied();
 	}
@@ -429,8 +377,6 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Provider<Batch> batchProvider = context.mock(Provider.class);
 		context.checking(new Expectations() {{
-			oneOf(database).open(false);
-			oneOf(cleaner).startCleaning();
 			// addLocallyGeneratedMessage(message)
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -448,15 +394,11 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(0));
 			oneOf(database).setSendability(txn, messageId, 0);
 			oneOf(database).commitTransaction(txn);
-			oneOf(cleaner).stopCleaning();
-			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				batchProvider);
 
-		db.open(false);
 		db.addLocallyGeneratedMessage(message);
-		db.close();
 
 		context.assertIsSatisfied();
 	}
@@ -471,8 +413,6 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Provider<Batch> batchProvider = context.mock(Provider.class);
 		context.checking(new Expectations() {{
-			oneOf(database).open(false);
-			oneOf(cleaner).startCleaning();
 			// addLocallyGeneratedMessage(message)
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -493,27 +433,40 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).getParent(txn, messageId);
 			will(returnValue(MessageId.NONE));
 			oneOf(database).commitTransaction(txn);
-			oneOf(cleaner).stopCleaning();
-			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				batchProvider);
 
-		db.open(false);
 		db.addLocallyGeneratedMessage(message);
-		db.close();
 
 		context.assertIsSatisfied();
 	}
 
-	private byte[] getRandomId() {
-		byte[] b = new byte[32];
-		random.nextBytes(b);
-		return b;
-	}
+	@Test
+	public void testGenerateBundleThrowsExceptionIfContactIsMissing()
+	throws DbException {
+		Mockery context = new Mockery();
+		@SuppressWarnings("unchecked")
+		final Database<Object> database = context.mock(Database.class);
+		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		@SuppressWarnings("unchecked")
+		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Bundle bundle = context.mock(Bundle.class);
+		context.checking(new Expectations() {{
+			oneOf(database).startTransaction();
+			will(returnValue(txn));
+			oneOf(database).containsContact(txn, contactId);
+			will(returnValue(false));
+			oneOf(database).commitTransaction(txn);
+		}});
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				batchProvider);
 
-	@After
-	public void tearDown() {
-		TestUtils.deleteTestDirectory(testDir);
+		try {
+			db.generateBundle(contactId, bundle);
+			assertTrue(false);
+		} catch(NoSuchContactException expected) {}
+
+		context.assertIsSatisfied();
 	}
 }
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index 483b507af2c0dae8b13848c00d44626e109b945d..3c18e2314bbf37a756298d86f3864d4c93199118 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -53,11 +53,11 @@ public class H2DatabaseTest extends TestCase {
 
 	public H2DatabaseTest() {
 		super();
-		authorId = new AuthorId(getRandomId());
-		batchId = new BatchId(getRandomId());
+		authorId = new AuthorId(TestUtils.getRandomId());
+		batchId = new BatchId(TestUtils.getRandomId());
 		contactId = new ContactId(123);
-		groupId = new GroupId(getRandomId());
-		messageId = new MessageId(getRandomId());
+		groupId = new GroupId(TestUtils.getRandomId());
+		messageId = new MessageId(TestUtils.getRandomId());
 		timestamp = System.currentTimeMillis();
 		size = 1234;
 		body = new byte[size];
@@ -338,7 +338,7 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testBatchesToAck() throws DbException {
-		BatchId batchId1 = new BatchId(getRandomId());
+		BatchId batchId1 = new BatchId(TestUtils.getRandomId());
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
 
@@ -461,13 +461,13 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testRetransmission() throws DbException {
-		BundleId bundleId = new BundleId(getRandomId());
-		BundleId bundleId1 = new BundleId(getRandomId());
-		BundleId bundleId2 = new BundleId(getRandomId());
-		BundleId bundleId3 = new BundleId(getRandomId());
-		BundleId bundleId4 = new BundleId(getRandomId());
-		BatchId batchId1 = new BatchId(getRandomId());
-		BatchId batchId2 = new BatchId(getRandomId());
+		BundleId bundleId = new BundleId(TestUtils.getRandomId());
+		BundleId bundleId1 = new BundleId(TestUtils.getRandomId());
+		BundleId bundleId2 = new BundleId(TestUtils.getRandomId());
+		BundleId bundleId3 = new BundleId(TestUtils.getRandomId());
+		BundleId bundleId4 = new BundleId(TestUtils.getRandomId());
+		BatchId batchId1 = new BatchId(TestUtils.getRandomId());
+		BatchId batchId2 = new BatchId(TestUtils.getRandomId());
 		Set<MessageId> empty = Collections.emptySet();
 		Mockery context = new Mockery();
 		MessageFactory messageFactory = context.mock(MessageFactory.class);
@@ -508,8 +508,8 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testGetMessagesByAuthor() throws DbException {
-		AuthorId authorId1 = new AuthorId(getRandomId());
-		MessageId messageId1 = new MessageId(getRandomId());
+		AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
+		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
 		Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId,
 				authorId1, timestamp, body);
 		Mockery context = new Mockery();
@@ -543,10 +543,10 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testGetNumberOfSendableChildren() throws DbException {
-		MessageId childId1 = new MessageId(getRandomId());
-		MessageId childId2 = new MessageId(getRandomId());
-		MessageId childId3 = new MessageId(getRandomId());
-		GroupId groupId1 = new GroupId(getRandomId());
+		MessageId childId1 = new MessageId(TestUtils.getRandomId());
+		MessageId childId2 = new MessageId(TestUtils.getRandomId());
+		MessageId childId3 = new MessageId(TestUtils.getRandomId());
+		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
 		Message child1 = new MessageImpl(childId1, messageId, groupId,
 				authorId, timestamp, body);
 		Message child2 = new MessageImpl(childId2, messageId, groupId,
@@ -588,7 +588,7 @@ public class H2DatabaseTest extends TestCase {
 
 	@Test
 	public void testGetOldMessages() throws DbException {
-		MessageId messageId1 = new MessageId(getRandomId());
+		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
 		Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId,
 				authorId, timestamp + 1000, body);
 		Mockery context = new Mockery();
@@ -758,12 +758,6 @@ public class H2DatabaseTest extends TestCase {
 		TestUtils.deleteTestDirectory(testDir);
 	}
 
-	private byte[] getRandomId() {
-		byte[] b = new byte[32];
-		random.nextBytes(b);
-		return b;
-	}
-
 	private static class TestMessageFactory implements MessageFactory {
 
 		public Message createMessage(MessageId id, MessageId parent,
diff --git a/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java b/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java
index 8de0dc1802bd98ed242a6f33396eaccf28fd76ef..16ff7eae5efa4073931766bb758d4e33df1e9da1 100644
--- a/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java
+++ b/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java
@@ -5,12 +5,20 @@ import net.sf.briar.api.protocol.Batch;
 
 import com.google.inject.Provider;
 
-public class ReadWriteLockDatabaseComponentTest extends DatabaseComponentTest {
+public class ReadWriteLockDatabaseComponentTest
+extends DatabaseComponentImplTest {
 
 	@Override
 	protected <T> DatabaseComponent createDatabaseComponent(
 			Database<T> database, DatabaseCleaner cleaner,
 			Provider<Batch> batchProvider) {
+		return createDatabaseComponentImpl(database, cleaner, batchProvider);
+	}
+
+	@Override
+	protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
+			Database<T> database, DatabaseCleaner cleaner,
+			Provider<Batch> batchProvider) {
 		return new ReadWriteLockDatabaseComponent<T>(database, cleaner,
 				batchProvider);
 	}
diff --git a/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java b/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java
index 758ce1ce4815fb106d83b6dab454db08448487a3..647f50490d9296c101f6c69f1f30c84392b471b6 100644
--- a/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java
+++ b/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java
@@ -5,14 +5,21 @@ import net.sf.briar.api.protocol.Batch;
 
 import com.google.inject.Provider;
 
-public class SynchronizedDatabaseComponentTest extends DatabaseComponentTest {
+public class SynchronizedDatabaseComponentTest
+extends DatabaseComponentImplTest {
 
 	@Override
 	protected <T> DatabaseComponent createDatabaseComponent(
 			Database<T> database, DatabaseCleaner cleaner,
 			Provider<Batch> batchProvider) {
+		return createDatabaseComponentImpl(database, cleaner, batchProvider);
+	}
+
+	@Override
+	protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
+			Database<T> database, DatabaseCleaner cleaner,
+			Provider<Batch> batchProvider) {
 		return new SynchronizedDatabaseComponent<T>(database, cleaner,
 				batchProvider);
 	}
-
 }