diff --git a/tests/fixtures/assert.hpp b/tests/fixtures/assert.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..63defc9dd7c3561ec0560f041257002207a7924a
--- /dev/null
+++ b/tests/fixtures/assert.hpp
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright © 2021 Florian Fischer
+#pragma once
+
+#include "Common.hpp"
+
+#define ASSERT(exp)  \
+	{                  \
+		if (!(exp)) DIE; \
+	}
+#define ASSERT_MSG(exp, msg)  \
+	{                           \
+		if (!(exp)) DIE_MSG(msg); \
+	}
diff --git a/tests/io/AlarmFutureTest.cpp b/tests/io/AlarmFutureTest.cpp
index 7ff4e6f278b658633c37784b1129ceb56616ad19..3b5476fa1e6beb993515d2f757709abadcd668a3 100644
--- a/tests/io/AlarmFutureTest.cpp
+++ b/tests/io/AlarmFutureTest.cpp
@@ -1,11 +1,11 @@
 // SPDX-License-Identifier: LGPL-3.0-or-later
 // Copyright © 2020-2021 Florian Fischer
-#include <cassert>	// for assert
 #include <cerrno>		// for ETIME
 #include <chrono>		// for microseconds, duration_cast, operator-
 #include <cstdint>	// for int32_t
 
 #include "emper-config.h"
+#include "fixtures/assert.hpp"
 #include "io/Future.hpp"	// for AlarmFuture
 
 #ifdef EMPER_HAS_COMPARE_H
@@ -23,8 +23,11 @@ void emperTest() {
 	auto end = std::chrono::high_resolution_clock::now();
 
 	// timeouts return -ETIME if they trigger
-	assert(res == -ETIME);
+	ASSERT(res == -ETIME);
 
-	assert(std::chrono::duration_cast<std::chrono::microseconds>(end - start) >=
+	// If have no idea why clang-tidy things we are using a null pointer here.
+	// Even if clang tidy is just smarter than me I have no idea how to modernize this.
+	// NOLINTNEXTLINE(modernize-use-nullptr)
+	ASSERT(std::chrono::duration_cast<std::chrono::microseconds>(end - start) >=
 				 std::chrono::seconds(1));
 }
diff --git a/tests/io/CancelFutureTest.cpp b/tests/io/CancelFutureTest.cpp
index 829a8a9f3ff2e3fa27f98d33c0a1b6797973100f..afc4f4d4aba38d4b34d007f300e8d126283639f3 100644
--- a/tests/io/CancelFutureTest.cpp
+++ b/tests/io/CancelFutureTest.cpp
@@ -2,11 +2,11 @@
 // Copyright © 2020-2021 Florian Fischer
 #include <sys/eventfd.h>	// for eventfd, EFD_SEMAPHORE
 
-#include <cassert>	// for assert
 #include <cerrno>		// for ECANCELED, ETIME
 #include <cstdint>	// for uint64_t, int32_t
 
-#include "Common.hpp"			// for DIE_MSG_ERRNO
+#include "Common.hpp"
+#include "fixtures/assert.hpp"
 #include "io/Future.hpp"	// for ReadFuture, WriteFuture
 
 using emper::io::ReadFuture;
@@ -18,21 +18,21 @@ uint64_t write_buf = 42;
 
 void cancelNotSubmitted() {
 	ReadFuture readFuture(efd, &read_buf, sizeof(read_buf), 0);
-	assert(readFuture.cancel() == -ENOENT);
+	ASSERT(readFuture.cancel() == -ENOENT);
 }
 
 void cancelSubmittedNotCompleted() {
 	ReadFuture readFuture(efd, &read_buf, sizeof(read_buf), 0);
 	readFuture.submit();
-	assert(readFuture.cancel() == -ECANCELED);
+	ASSERT(readFuture.cancel() == -ECANCELED);
 }
 
 void cancelCompleted() {
 	ReadFuture readFuture(efd, &read_buf, sizeof(read_buf), 0);
 	readFuture.submit();
 	WriteFuture writeFuture(efd, &write_buf, sizeof(write_buf), 0);
-	assert(writeFuture.submitAndWait() == sizeof(write_buf));
-	assert(readFuture.cancel() == sizeof(write_buf) && read_buf == write_buf);
+	ASSERT(writeFuture.submitAndWait() == sizeof(write_buf));
+	ASSERT(readFuture.cancel() == sizeof(write_buf) && read_buf == write_buf);
 }
 
 void cancelNotCompletedChain() {
@@ -41,8 +41,8 @@ void cancelNotCompletedChain() {
 	readFuture2.setDependency(readFuture);
 
 	readFuture2.submit();
-	assert(readFuture2.cancel() == -ECANCELED);
-	assert(readFuture.wait() == -ECANCELED);
+	ASSERT(readFuture2.cancel() == -ECANCELED);
+	ASSERT(readFuture.wait() == -ECANCELED);
 }
 
 void cancelPartialCompletedChain() {
@@ -51,8 +51,8 @@ void cancelPartialCompletedChain() {
 	readFuture2.setDependency(readFuture);
 
 	readFuture2.submit();
-	assert(readFuture2.cancel() == -ECANCELED);
-	assert(readFuture.wait() == -ECANCELED);
+	ASSERT(readFuture2.cancel() == -ECANCELED);
+	ASSERT(readFuture.wait() == -ECANCELED);
 }
 
 void cancelNotCompletedFutureChain() {
@@ -62,11 +62,11 @@ void cancelNotCompletedFutureChain() {
 	readFuture2.setDependency(readFuture);
 
 	readFuture2.submit();
-	assert(writeFuture.submitAndWait() == sizeof(write_buf));
+	ASSERT(writeFuture.submitAndWait() == sizeof(write_buf));
 	// TODO: investigate why this read is completed with -EINTR most of the time
 	int r = readFuture2.cancel();
-	assert(r == -EINTR || r == -ECANCELED);
-	assert(readFuture.wait() == sizeof(write_buf) && read_buf == write_buf);
+	ASSERT(r == -EINTR || r == -ECANCELED);
+	ASSERT(readFuture.wait() == sizeof(write_buf) && read_buf == write_buf);
 }
 
 void emperTest() {
diff --git a/tests/io/FutureCallbackTest.cpp b/tests/io/FutureCallbackTest.cpp
index a4a1dcd229d673d72302d6c5b588fa63c8822209..4105efb2a48939c90d43c35029b75103b740c336 100644
--- a/tests/io/FutureCallbackTest.cpp
+++ b/tests/io/FutureCallbackTest.cpp
@@ -1,10 +1,10 @@
 // SPDX-License-Identifier: LGPL-3.0-or-later
 // Copyright © 2020-2021 Florian Fischer
-#include <cassert>	// for assert
 #include <cerrno>		// for ETIME
 #include <cstdint>	// for int32_t
 
 #include "BinaryPrivateSemaphore.hpp"
+#include "fixtures/assert.hpp"
 #include "io/Future.hpp"	// for AlarmFuture
 
 using emper::io::AlarmFuture;
@@ -16,7 +16,7 @@ void emperTest() {
 	BPS bps;
 
 	alarm.setCallback([&bps](int32_t res) {
-		assert(res == -ETIME);
+		ASSERT(res == -ETIME);
 		bps.signal();
 	});
 	alarm.submit();
diff --git a/tests/io/IncrementalCompletionTest.cpp b/tests/io/IncrementalCompletionTest.cpp
index 288ad2a64e6ce5be7ed278fae640808779a1045d..c24f77d41b15cde50532a3d68e7afa0dcd099d07 100644
--- a/tests/io/IncrementalCompletionTest.cpp
+++ b/tests/io/IncrementalCompletionTest.cpp
@@ -2,11 +2,11 @@
 // Copyright © 2020 Florian Fischer
 #include <unistd.h>	 // for pipe
 
-#include <cassert>	// for assert
 #include <cstdint>	// for uint64_t, int32_t
 #include <cstring>	// for memcmp
 
-#include "Common.hpp"			// for DIE_MSG_ERRNO, DIE_MSG
+#include "Common.hpp"
+#include "fixtures/assert.hpp"
 #include "io/Future.hpp"	// for ReadFuture, CloseFuture, WriteFuture
 
 using emper::io::CloseFuture;
@@ -32,18 +32,18 @@ void emperTest() {
 	readFuture.submit();
 
 	int32_t res = writeFuture.wait();
-	assert(res == MEMSIZE);
+	ASSERT(res == MEMSIZE);
 
 	res = readFuture.wait();
-	assert(res == MEMSIZE);
+	ASSERT(res == MEMSIZE);
 
-	assert(memcmp(memOut, memIn, MEMSIZE) == 0);
+	ASSERT(memcmp(memOut, memIn, MEMSIZE) == 0);
 
 	CloseFuture closeWriteSide(pipefds[1]);
-	assert(closeWriteSide.submitAndWait() == 0);
+	ASSERT(closeWriteSide.submitAndWait() == 0);
 
 	CloseFuture closeReadSide(pipefds[0]);
-	assert(closeReadSide.submitAndWait() == 0);
+	ASSERT(closeReadSide.submitAndWait() == 0);
 
 	delete[] memOut;
 	delete[] memIn;
diff --git a/tests/io/LinkFutureTest.cpp b/tests/io/LinkFutureTest.cpp
index 94291c21af6eeef1ee696c9f155ee0a227d6f410..a5f4fb6588599755c8a86069ab8e874c0ce9168a 100644
--- a/tests/io/LinkFutureTest.cpp
+++ b/tests/io/LinkFutureTest.cpp
@@ -5,12 +5,12 @@
 #include <sys/types.h>		// for ssize_t
 
 #include <array>
-#include <cassert>	// for assert
 #include <cerrno>		// for EBADF, ECANCELED
 #include <cstdint>	// for uint64_t, int32_t
 
 #include "Common.hpp"	 // for DIE_MSG_ERRNO, DIE_MSG
 #include "Debug.hpp"
+#include "fixtures/assert.hpp"
 #include "io.hpp"
 #include "io/Future.hpp"	// for ReadFuture, CloseFuture, WriteFuture
 
@@ -91,10 +91,10 @@ static void failureChainInvCor() {
 	readFuture.setDependency(invalidReadFuture);
 
 	int32_t res = readFuture.submitAndWait();
-	assert(res == -ECANCELED);
+	ASSERT(res == -ECANCELED);
 
 	res = invalidReadFuture.wait();
-	assert(res == -EBADF);
+	ASSERT(res == -EBADF);
 }
 
 static void failureChainCorInvCor() {
@@ -102,7 +102,7 @@ static void failureChainCorInvCor() {
 
 	int fd = emper::io::openAndWait("/dev/null", O_WRONLY);
 	// int fd = open("/dev/null", O_WRONLY, 0);
-	assert(fd != -1);
+	ASSERT(fd != -1);
 
 	WriteFuture correctFuture1(fd, buf.data(), buf.size(), 0);
 
@@ -113,16 +113,16 @@ static void failureChainCorInvCor() {
 	correctFuture2.setDependency(invalidFuture);
 
 	int32_t res = correctFuture2.submitAndWait();
-	assert(res == -ECANCELED);
+	ASSERT(res == -ECANCELED);
 
 	res = invalidFuture.wait();
-	assert(res == -EBADF);
+	ASSERT(res == -EBADF);
 
 	res = correctFuture1.wait();
 	// Since the kernel commit cf10960426515 io_uring does not
 	// submit broken links and completes any request before a
 	// invalid one as canceled.
-	assert(res == -ECANCELED || res == (int32_t)buf.size());
+	ASSERT(res == -ECANCELED || res == (int32_t)buf.size());
 
 	emper::io::closeAndWait(fd);
 }
diff --git a/tests/io/SimpleDiskAndNetworkTest.cpp b/tests/io/SimpleDiskAndNetworkTest.cpp
index ee87b8d276e6e86876685efa6c4b76832fb2b9c9..145773fb1457c23bfe8ada191b173ffbb42a0076 100644
--- a/tests/io/SimpleDiskAndNetworkTest.cpp
+++ b/tests/io/SimpleDiskAndNetworkTest.cpp
@@ -7,7 +7,6 @@
 #include <sys/socket.h>								// for bind, listen, setsockopt
 #include <unistd.h>										// for close
 
-#include <cassert>	// for assert
 #include <cstdlib>	// for mkstemp
 #include <cstring>	// for memcmp, memset
 #include <memory>		// for allocator, unique_ptr
@@ -18,6 +17,7 @@
 #include "CountingPrivateSemaphore.hpp"	 // for CPS
 #include "Runtime.hpp"
 #include "emper.hpp"
+#include "fixtures/assert.hpp"
 #include "fixtures/network.hpp"
 #include "io.hpp"
 #include "io/Future.hpp"
@@ -129,10 +129,10 @@ static void server_func(int sockfd) {
 			DIE_MSG_ERRNO("read failed");
 		}
 
-		assert(written == bytes_read);
+		ASSERT(written == bytes_read);
 
-		assert(memcmp(read_buf, iov[0].iov_base, iov[0].iov_len) == 0);
-		assert(memcmp((char*)read_buf + iov[0].iov_len, iov[1].iov_base, iov[1].iov_len) == 0);
+		ASSERT(memcmp(read_buf, iov[0].iov_base, iov[0].iov_len) == 0);
+		ASSERT(memcmp((char*)read_buf + iov[0].iov_len, iov[1].iov_base, iov[1].iov_len) == 0);
 
 		close(file_fd);
 	}
diff --git a/tests/io/TimeoutTest.cpp b/tests/io/TimeoutTest.cpp
index 9e9f7d3f1b4f5958606c0e195a283d7b4395034b..f551f7d1d75b65c7db5a316c9fb38d7ac1c556b9 100644
--- a/tests/io/TimeoutTest.cpp
+++ b/tests/io/TimeoutTest.cpp
@@ -5,7 +5,6 @@
 #include <sys/socket.h>
 #include <sys/types.h>
 
-#include <cassert>
 #include <cerrno>
 #include <cstdint>
 #include <cstring>
@@ -15,6 +14,7 @@
 #include "Debug.hpp"
 #include "Future.hpp"
 #include "emper.hpp"
+#include "fixtures/assert.hpp"
 #include "io.hpp"
 
 using emper::io::ReadFuture;
@@ -71,8 +71,8 @@ void sockTest() {
 	DBG("submit recv");
 	TimeoutWrapper::Timespec ts = {.tv_sec = 1, .tv_nsec = 0};
 	ssize_t res = emper::io::recvAndTryWait(sock1, &recvBuf, sizeof(recvBuf), 0, ts);
-	assert(res == -1);
-	assert(errno == ECANCELED);
+	ASSERT(res == -1);
+	ASSERT(errno == ECANCELED);
 
 	// TODO: find a way to test sendAndTryWait
 	// // allocate a huge buffer which is surely bigger then the sockets buffer and
@@ -81,8 +81,8 @@ void sockTest() {
 	// auto* sendBuf = new char[MEMB];
 	// DBG("submit send");
 	// res = emper::io::sendAndTryWait(sock1, &sendBuf, MEMB, 0, ts, true);
-	// assert(res == -1);
-	// assert(errno == ECANCELED);
+	// ASSERT(res == -1);
+	// ASSERT(errno == ECANCELED);
 	// delete[] sendBuf;
 	emper::io::closeAndForget(sock1);
 	emper::io::closeAndForget(sock2);
@@ -97,8 +97,8 @@ void readTest() {
 	uint64_t readBuf;
 	TimeoutWrapper::Timespec ts = {.tv_sec = 1, .tv_nsec = 0};
 	ssize_t res = emper::io::readFileAndTryWait(efd, &readBuf, sizeof(readBuf), ts);
-	assert(res == -1);
-	assert(errno == ECANCELED);
+	ASSERT(res == -1);
+	ASSERT(errno == ECANCELED);
 
 	emper::io::closeAndForget(efd);
 }
@@ -118,10 +118,10 @@ void writeTest() {
 	writeBuf = 1;
 	TimeoutWrapper::Timespec ts = {.tv_sec = 1, .tv_nsec = 0};
 	ssize_t res = emper::io::writeFileAndTryWait(efd, &writeBuf, sizeof(writeBuf), ts);
-	assert(res == -1);
+	ASSERT(res == -1);
 	// write requests can't be canceled when in execution so this
 	// will return as interupted
-	assert(errno == EINTR);
+	ASSERT(errno == EINTR);
 
 	emper::io::closeAndForget(efd);
 }
diff --git a/tests/io/TimeoutWrapperTest.cpp b/tests/io/TimeoutWrapperTest.cpp
index ef7e3b36eda94e864ace3c20ac47d4935dce47ea..02b8b64aaa5fb594b237783977206fcbe0d8bf9c 100644
--- a/tests/io/TimeoutWrapperTest.cpp
+++ b/tests/io/TimeoutWrapperTest.cpp
@@ -2,11 +2,11 @@
 // Copyright © 2020-2021 Florian Fischer
 #include <sys/eventfd.h>	// for eventfd, EFD_SEMAPHORE
 
-#include <cassert>	// for assert
 #include <cerrno>		// for ECANCELED, ETIME
 #include <cstdint>	// for uint64_t, int32_t
 
-#include "Common.hpp"			// for DIE_MSG_ERRNO
+#include "Common.hpp"
+#include "fixtures/assert.hpp"
 #include "io/Future.hpp"	// for ReadFuture, TimeoutWrapper
 
 using emper::io::ReadFuture;
@@ -25,8 +25,8 @@ void emperTest() {
 	TimeoutWrapper t(readFuture, ts);
 
 	int32_t res = t.submitAndWait();
-	assert(res == -ETIME);
+	ASSERT(res == -ETIME);
 
 	res = readFuture.wait();
-	assert(res == -ECANCELED);
+	ASSERT(res == -ECANCELED);
 }
diff --git a/tests/meson.build b/tests/meson.build
index f2d4ef05966c34b216ebc1405d402d5efde34508..f976cf881c26e027cdd656e0846d3271a0a5f372 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -109,7 +109,6 @@ tests = [
 subdir('io')
 subdir('lib')
 
-undef_ndebug = '-UNDEBUG'
 test_env = environment(
   {
 	# Set glibc's MALLOC_PERTURB to 1. This means that newly allocated
@@ -162,8 +161,6 @@ foreach test_dict : tests
   test_exe = executable(test_name,
 						source,
 						include_directories: emper_all_include,
-						c_args: undef_ndebug,
-						cpp_args: undef_ndebug,
 						dependencies: [emper_full_dep] + test_deps,
 					   )