diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerIntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerIntegrationTest.kt
index ffa34af39653988f58da63d9a0d8849287791240..aa9f5454182b86bec474106d88376dbfcdedf349 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerIntegrationTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerIntegrationTest.kt
@@ -27,8 +27,8 @@ import kotlin.test.assertEquals
 class ContactsManagerIntegrationTest : IntegrationTest() {
 
     @BeforeEach
-    override fun initDb() {
-        super.initDb()
+    override fun beforeEach() {
+        super.beforeEach()
         addOwnerToken()
     }
 
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerMalformedInputIntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerMalformedInputIntegrationTest.kt
index 0f7456792197b2a6bd14f3f0ec549b1f4dc470c5..6acd4fa506e2b53d9a21a3cf0cb8a726e9c48b59 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerMalformedInputIntegrationTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerMalformedInputIntegrationTest.kt
@@ -19,8 +19,8 @@ import kotlin.test.assertEquals
 class ContactsManagerMalformedInputIntegrationTest : IntegrationTest(false) {
 
     @BeforeEach
-    override fun initDb() {
-        super.initDb()
+    override fun beforeEach() {
+        super.beforeEach()
         addOwnerToken()
     }
 
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt
index fd0d67b97374bf1a1ce6a4865ea2cbe3fa801430..cd92398a51cfbb33d007f1e377cbac9b4e55260a 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt
@@ -23,8 +23,8 @@ class FileManagerIntegrationTest : IntegrationTest() {
     private val bytes = Random.nextBytes(2048)
 
     @BeforeEach
-    override fun initDb() {
-        super.initDb()
+    override fun beforeEach() {
+        super.beforeEach()
         addOwnerToken()
         addContact(contact1)
         addContact(contact2)
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt
index bbb9f151b9da66d78110d5b3687f2be17be3807c..a628a963de01f3e3064be432464ce2de401e5898 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt
@@ -22,7 +22,10 @@ import org.junit.jupiter.api.BeforeAll
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.TestInstance
 import org.junit.jupiter.api.TestInstance.Lifecycle
+import org.junit.jupiter.api.fail
 import org.junit.jupiter.api.io.TempDir
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
 import java.io.File
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
@@ -30,12 +33,16 @@ import kotlin.test.assertTrue
 @TestInstance(Lifecycle.PER_CLASS)
 abstract class IntegrationTest(private val installJsonFeature: Boolean = true) {
 
+    companion object {
+        private val LOG: Logger = LoggerFactory.getLogger(IntegrationTest::class.java)
+    }
+
     protected lateinit var testComponent: TestComponent
     protected val db by lazy { testComponent.getDatabase() }
     private val lifecycleManager by lazy { testComponent.getLifecycleManager() }
     protected val setupManager by lazy { testComponent.getSetupManager() }
     protected val metadataManager by lazy { testComponent.getMetadataManager() }
-    private val wipeManager by lazy { testComponent.getWipeManager() }
+    protected val wipeManager by lazy { testComponent.getWipeManager() }
     protected val httpClient = HttpClient(CIO) {
         expectSuccess = false // prevents exceptions on non-success responses
         if (installJsonFeature) {
@@ -55,6 +62,18 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) {
     protected val contact1 = getNewRandomContact()
     protected val contact2 = getNewRandomContact()
 
+    @Volatile
+    protected var exceptionInBackgroundThread = false
+
+    init {
+        // Ensure exceptions thrown on worker threads cause tests to fail
+        val fail = Thread.UncaughtExceptionHandler { _: Thread?, throwable: Throwable ->
+            LOG.warn("Caught unhandled exception", throwable)
+            exceptionInBackgroundThread = true
+        }
+        Thread.setDefaultUncaughtExceptionHandler(fail)
+    }
+
     @BeforeAll
     fun setUp(@TempDir tempDir: File) {
         testComponent = DaggerTestComponent.builder().testModule(TestModule(tempDir)).build()
@@ -71,7 +90,8 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) {
     }
 
     @BeforeEach
-    open fun initDb() {
+    open fun beforeEach() {
+        exceptionInBackgroundThread = false
         // need to reopen database here because we're closing it after each test
         db.open(null)
         db.read { txn ->
@@ -82,9 +102,19 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) {
     }
 
     @AfterEach
-    open fun wipe() {
-        wipeManager.wipeDatabaseAndFiles()
-        assertFalse(setupManager.hasDb)
+    open fun afterEach() {
+        afterEach(true)
+    }
+
+    fun afterEach(wipe: Boolean) {
+        if (wipe) {
+            wipeManager.wipeDatabaseAndFiles()
+            assertFalse(setupManager.hasDb)
+        }
+
+        if (exceptionInBackgroundThread) {
+            fail("background thread has thrown an exception unexpectedly")
+        }
     }
 
     protected fun addOwnerToken() {
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/settings/MetadataRouteManagerTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/settings/MetadataRouteManagerTest.kt
index 726a6f8cbf6cd9ce343b04c04cdc60e5e64de7d4..71fa450b14929c1836a3fe0bab9e5c395f2f776c 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/settings/MetadataRouteManagerTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/settings/MetadataRouteManagerTest.kt
@@ -13,8 +13,8 @@ import kotlin.test.assertEquals
 class MetadataRouteManagerTest : IntegrationTest() {
 
     @BeforeEach
-    override fun initDb() {
-        super.initDb()
+    override fun beforeEach() {
+        super.beforeEach()
         addOwnerToken()
         addContact(contact1)
         addContact(contact2)
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest1.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest1.kt
index e140ae19892692057770cc517bc2df8d397e7f37..11b79b0fb254374d434b16f888b04d9258222fa0 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest1.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest1.kt
@@ -8,9 +8,9 @@ import kotlin.concurrent.thread
 class WipeTest1 : IntegrationTest() {
 
     @Test
-    fun test() {
+    fun `wiping and concurrent database access doesn't cause deadlocks`() {
         val t1 = thread(name = "dropper") {
-            db.dropAllTablesAndClose()
+            wipeManager.wipeDatabaseAndFiles()
         }
         // This most likely throws a DbException within the thread, but if it does is doesn't make
         // the test fail.
@@ -19,13 +19,17 @@ class WipeTest1 : IntegrationTest() {
         }
         t1.join()
         t2.join()
+
+        // reset flag for exceptions thrown on background threads as this can indeed happen here
+        // and is OK
+        exceptionInBackgroundThread = false
     }
 
     @AfterEach
-    override fun clearDb() {
-        // This is not expected to work because calling dropAllTablesAndClose() on a closed Database
+    override fun afterEach() {
+        // We need to pass false here because calling wipeDatabaseAndFiles() with a closed Database
         // throws a DbClosedException.
-        // super.clearDb()
+        afterEach(false)
     }
 
 }
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest2.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest2.kt
index d7e0ae452d9eb0f837a310c702497de2e49daba3..14de328da47b88123d7ce5b5d850e10342266fba 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest2.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest2.kt
@@ -22,11 +22,11 @@ class WipeTest2 : IntegrationTest() {
     }
 
     @Test
-    fun test() {
+    fun `concurrent wipe requests don't interfere and database access doesn't cause deadlocks`() {
         // One of the two dropper threads will succeed, the other will fail.
         val t1 = thread(name = "dropper 1") {
             try {
-                db.dropAllTablesAndClose()
+                wipeManager.wipeDatabaseAndFiles()
                 incrementSuccess()
             } catch (t: Throwable) {
                 incrementFailure()
@@ -34,7 +34,7 @@ class WipeTest2 : IntegrationTest() {
         }
         val t2 = thread(name = "dropper 2") {
             try {
-                db.dropAllTablesAndClose()
+                wipeManager.wipeDatabaseAndFiles()
                 incrementSuccess()
             } catch (t: Throwable) {
                 incrementFailure()
@@ -50,13 +50,17 @@ class WipeTest2 : IntegrationTest() {
         t3.join()
         assertEquals(1, succeeded)
         assertEquals(1, failed)
+
+        // reset flag for exceptions thrown on background threads as this can indeed happen here
+        // and is OK
+        exceptionInBackgroundThread = false
     }
 
     @AfterEach
-    override fun clearDb() {
-        // This is not expected to work because calling dropAllTablesAndClose() on a closed Database
+    override fun afterEach() {
+        // We need to pass false here because calling wipeDatabaseAndFiles() with a closed Database
         // throws a DbClosedException.
-        // super.clearDb()
+        afterEach(false)
     }
 
 }