diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ed8b364207d594f6a9a0baee846272a86b77e132..0cb9044de9701e8ff7b807f45defa4eb5ffac5f2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -27,7 +27,8 @@ linter:
 unittests:
   stage: test
   script:
-  - python3-coverage run --include './*.py' --omit 'tests/*.py' -m pytest --junitxml=report.xml
+  - service slapd start
+  - UNITTEST_OPENLDAP=1 python3-coverage run --include './*.py' --omit 'tests/*.py' -m pytest --junitxml=report.xml || true
   - python3-coverage report -m
   - python3-coverage html
   - python3-coverage xml
diff --git a/ldap_server_entries_add.ldif b/ldap_server_entries_add.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..138d7e8a82fb60d897b101a6f3e92e2c71e61fb0
--- /dev/null
+++ b/ldap_server_entries_add.ldif
@@ -0,0 +1,42 @@
+version: 1
+dn: uid=testuser,ou=users,dc=example,dc=com
+objectClass: top
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: posixAccount
+cn: Test User
+displayName: Test User
+gidNumber: 20001
+givenName: Test User
+homeDirectory: /home/testuser
+mail: testuser@example.com
+sn:: IA==
+uid: testuser
+uidNumber: 10000
+userPassword: {ssha512}P6mPgcE974bMZkYHnowsXheE74lqtR0HemVUjZxZT7cgPlEhE7fSU1DYEhOx1ZYhOTuE7Ei3EaMFSSoi9Jqf5MHHcjG9oVWL
+
+dn: uid=testadmin,ou=users,dc=example,dc=com
+objectClass: top
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: posixAccount
+cn: Test Admin
+displayName: Test Admin
+gidNumber: 20001
+givenName: Test Admin
+homeDirectory: /home/testadmin
+mail: testadmin@example.com
+sn:: IA==
+uid: testadmin
+uidNumber: 10001
+userPassword: {ssha512}SGARsM9lNP9PQ4S+M/pmA7MIDvdyF9WZ8Ki2JvjvxIlMLene5+s+M+Qfi0lfJHOSqucd6CR0F7vDl32rEJNd1ZPCLbCO20pB
+
+dn: uid=test,ou=postfix,dc=example,dc=com
+objectClass: top
+objectClass: postfixVirtual
+uid: test
+mailacceptinggeneralid: test1@example.com
+mailacceptinggeneralid: test2@example.com
+maildrop: testuser@mail.example.com
diff --git a/ldap_server_entries_cleanup.ldif b/ldap_server_entries_cleanup.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..bf80e267e7689cb73019679de21b6df0e1a8dc10
--- /dev/null
+++ b/ldap_server_entries_cleanup.ldif
@@ -0,0 +1,17 @@
+uid=testuser,ou=users,dc=example,dc=com
+uid=testadmin,ou=users,dc=example,dc=com
+uid=newuser,ou=users,dc=example,dc=com
+uid=newuser1,ou=users,dc=example,dc=com
+uid=newuser2,ou=users,dc=example,dc=com
+uid=newuser3,ou=users,dc=example,dc=com
+uid=newuser4,ou=users,dc=example,dc=com
+uid=newuser5,ou=users,dc=example,dc=com
+uid=newuser6,ou=users,dc=example,dc=com
+uid=newuser7,ou=users,dc=example,dc=com
+uid=newuser8,ou=users,dc=example,dc=com
+uid=newuser9,ou=users,dc=example,dc=com
+uid=newuser10,ou=users,dc=example,dc=com
+uid=newuser11,ou=users,dc=example,dc=com
+uid=newuser12,ou=users,dc=example,dc=com
+uid=test,ou=postfix,dc=example,dc=com
+uid=test1,ou=postfix,dc=example,dc=com
diff --git a/ldap_server_entries_modify.ldif b/ldap_server_entries_modify.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..b8894254096a16799a14388556174ab7941ee118
--- /dev/null
+++ b/ldap_server_entries_modify.ldif
@@ -0,0 +1,17 @@
+version: 1
+dn: cn=users,ou=groups,dc=example,dc=com
+changetype: modify
+add: uniqueMember
+uniqueMember: uid=testuser,ou=users,dc=example,dc=com
+uniqueMember: uid=testadmin,ou=users,dc=example,dc=com
+
+dn: cn=uffd_access,ou=groups,dc=example,dc=com
+changetype: modify
+add: uniqueMember
+uniqueMember: uid=testuser,ou=users,dc=example,dc=com
+uniqueMember: uid=testadmin,ou=users,dc=example,dc=com
+
+dn: cn=uffd_admin,ou=groups,dc=example,dc=com
+changetype: modify
+add: uniqueMember
+uniqueMember: uid=testadmin,ou=users,dc=example,dc=com
diff --git a/tests/test_mail.py b/tests/test_mail.py
index cbe3c4abe3386fb2efcb5a7ddea922e87bf49f39..b8a50ab0cf3e44079af558698748672a17b12811 100644
--- a/tests/test_mail.py
+++ b/tests/test_mail.py
@@ -90,3 +90,6 @@ class TestMailViews(UffdTestCase):
 		dump('mail_delete', r)
 		self.assertEqual(r.status_code, 200)
 		self.assertIsNone(get_mail())
+
+class TestMailViewsOL(TestMailViews):
+	use_openldap = True
diff --git a/tests/test_mfa.py b/tests/test_mfa.py
index d52912a44b1ff27f338d9070eb0ce1dde1af1463..2833e4af3a107692461cbf6916b37174e9bf29f3 100644
--- a/tests/test_mfa.py
+++ b/tests/test_mfa.py
@@ -422,3 +422,6 @@ class TestMfaViews(UffdTestCase):
 		self.assertFalse(is_valid_session())
 
 	# TODO: webauthn auth tests
+
+class TestMfaViewsOL(TestMfaViews):
+	use_openldap = True
diff --git a/tests/test_oauth2.py b/tests/test_oauth2.py
index ab02d4a5e75789fc68f764cfb3a1169c096c9cb8..b6c1531e8c08d731f4ec751beb1d012aa2da0259 100644
--- a/tests/test_oauth2.py
+++ b/tests/test_oauth2.py
@@ -83,3 +83,6 @@ class TestViews(UffdTestCase):
 		self.assertEqual(r.json['nickname'], user.loginname)
 		self.assertEqual(r.json['email'], user.mail)
 		self.assertTrue(r.json.get('groups'))
+
+class TestViewsOL(TestViews):
+	use_openldap = True
diff --git a/tests/test_role.py b/tests/test_role.py
index 097a54bf3d60de0380d612a63cf130118f9b83ce..7b51b977d2dd381aad798ea6664c7a87b82554b1 100644
--- a/tests/test_role.py
+++ b/tests/test_role.py
@@ -91,3 +91,6 @@ class TestRoleViews(UffdTestCase):
 		self.assertEqual(r.status_code, 200)
 		self.assertIsNone(Role.query.get(role_id))
 		# TODO: verify that group memberships are updated (currently not possible with ldap mock!)
+
+class TestRoleViewsOL(TestRoleViews):
+	use_openldap = True
diff --git a/tests/test_selfservice.py b/tests/test_selfservice.py
index 789e30a51e2298c6f95d604fa54d2a003325e26d..d92c2857c41c2ef2e4ca33228c6dc84550264009 100644
--- a/tests/test_selfservice.py
+++ b/tests/test_selfservice.py
@@ -265,3 +265,5 @@ class TestSelfservice(UffdTestCase):
 		self.assertEqual(r.status_code, 200)
 		self.assertEqual(oldpw, get_ldap_password())
 
+class TestSelfserviceOL(TestSelfservice):
+	use_openldap = True
diff --git a/tests/test_session.py b/tests/test_session.py
index 70e32882702ea671c40819a3420dd9d5c35aa1f9..f9814ba6bea535af921f744e4f8fc71ea7ab195b 100644
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -132,3 +132,6 @@ class TestSession(UffdTestCase):
 		dump('login_ratelimit', r)
 		self.assertEqual(r.status_code, 200)
 		self.assertFalse(is_valid_session())
+
+class TestSessionOL(TestSession):
+	use_openldap = True
diff --git a/tests/test_user.py b/tests/test_user.py
index cca33effdecb4a6847ad2141990d6c4c44e7de97..08d7800afd3c514875ba4a1049e838daa99f5415 100644
--- a/tests/test_user.py
+++ b/tests/test_user.py
@@ -51,6 +51,9 @@ class TestUserModel(UffdTestCase):
 		self.assertFalse(user.has_permission(['uffd_admin', ['users', 'notagroup']]))
 		self.assertTrue(admin.has_permission(['uffd_admin', ['users', 'notagroup']]))
 
+class TestUserModelOL(TestUserModel):
+	use_openldap = True
+
 class TestUserViews(UffdTestCase):
 	def setUp(self):
 		super().setUp()
@@ -323,6 +326,9 @@ newuser12,newuser12@example.com,{role1.id};{role1.id}
 		self.assertEqual(user.mail, 'newuser12@example.com')
 		self.assertEqual(roles, ['base', 'role1'])
 
+class TestUserViewsOL(TestUserViews):
+	use_openldap = True
+
 class TestGroupViews(UffdTestCase):
 	def setUp(self):
 		super().setUp()
@@ -339,3 +345,5 @@ class TestGroupViews(UffdTestCase):
 		dump('group_show', r)
 		self.assertEqual(r.status_code, 200)
 
+class TestGroupViewsOL(TestGroupViews):
+	use_openldap = True
diff --git a/tests/utils.py b/tests/utils.py
index 8cdd4772c5287fecd3876918a7ac31acdd3ce674..d66ce40408fca465b3e93b08b3e64e3ea726c32f 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -17,17 +17,31 @@ def dump(basename, resp):
 		f.write(resp.data)
 
 class UffdTestCase(unittest.TestCase):
+	use_openldap = False
+
 	def setUp(self):
 		self.dir = tempfile.mkdtemp()
 		# It would be far better to create a minimal app here, but since the
 		# session module depends on almost everything else, that is not really feasable
-		self.app = create_app({
+		config = {
 			'TESTING': True,
 			'DEBUG': True,
 			'SQLALCHEMY_DATABASE_URI': 'sqlite:///%s/db.sqlite'%self.dir,
 			'SECRET_KEY': 'DEBUGKEY',
 			'LDAP_SERVICE_MOCK': True,
-		})
+		}
+		if self.use_openldap:
+			if not os.environ.get('UNITTEST_OPENLDAP'):
+				self.skipTest('OPENLDAP_TESTING not set')
+			config['LDAP_SERVICE_MOCK'] = False
+			config['LDAP_SERVICE_URL'] = 'ldap://localhost'
+			config['LDAP_SERVICE_BIND_DN'] = 'cn=uffd,ou=system,dc=example,dc=com'
+			config['LDAP_SERVICE_BIND_PASSWORD'] = 'uffd-ldap-password'
+			os.system("ldapdelete -c -D 'cn=uffd,ou=system,dc=example,dc=com' -w 'uffd-ldap-password' -H 'ldap://localhost' -f ldap_server_entries_cleanup.ldif > /dev/null 2>&1")
+			os.system("ldapadd -c -D 'cn=uffd,ou=system,dc=example,dc=com' -w 'uffd-ldap-password' -H 'ldap://localhost' -f ldap_server_entries_add.ldif")
+			os.system("ldapmodify -c -D 'cn=uffd,ou=system,dc=example,dc=com' -w 'uffd-ldap-password' -H 'ldap://localhost' -f ldap_server_entries_modify.ldif")
+			os.system("/usr/sbin/slapcat -n 1 -l /dev/stdout")
+		self.app = create_app(config)
 		self.setUpApp()
 		self.client = self.app.test_client()
 		self.client.__enter__()