When writing applications that interchange information with LDAP directory services there is always the need to write integration tests for these components and services.

Therefore we need a the possibility to start-up an embedded LDAP server, fill it with test-data and control its life-cycle during the test-phases.

In the following tutorial I’d like to demonstrate two candidates that fulfil this purpose, the ApacheDS test integrations and a small library named embedded-ldap-junit.

running ldap tests in eclipse 1024x791
Figure 1. Running LDAP tests in Eclipse IDE

Test Data

To populate our directory servers with some test data, we’re using the following LDIF file providing organization structures and persons taken from my old tutorial "Creating a LDAP server for your development environment in 5 minutes".

This is our users-import.ldif used:

dn: dc=example,dc=com
objectClass: domain
objectClass: top
dc: example

dn: ou=Users,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Users

dn: ou=Groups,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Groups

dn: cn=Micha Kops,ou=Users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
cn: Micha Kops
sn: Kops
uid: mkops
userPassword: abcdefg

dn: cn=Santa Claus,ou=Users,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: Santa Claus
sn: Claus
uid: sclaus
userPassword: abcdefg

dn: cn=John Steinbeck,ou=Users,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: John Steinbeck
sn: Steinbeck
uid: jsteinbeck
userPassword: abcdefg

Embedded LDAP JUnit

Though being in an early stage of development, embedded-ldap-junit looks quite nice and provides us a JUnit rule for running an embedded LDAP server in your JUnit test and it is based on the UnboundID LDAP SDK.

Dependencies

We just need to add one dependency to our Maven project’s pom.xml:

<dependency>
  <groupId>org.zapodot</groupId>
  <artifactId>embedded-ldap-junit</artifactId>
  <version>0.5.2</version>
  <scope>test</scope>
</dependency>

Integration Test

This is what our integration test looks like.

The EmbeddedLdapRuleBuilder allows us to construct our JUnit rule, to import our test data from the LDIF file and to open a connection to this server.

package it;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

import java.util.List;

import org.junit.Rule;
import org.junit.Test;
import org.zapodot.junit.ldap.EmbeddedLdapRule;
import org.zapodot.junit.ldap.EmbeddedLdapRuleBuilder;

import com.unboundid.ldap.sdk.LDAPInterface;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;

/**
 * Test using objectclass: org.zapodot:embedded-ldap-junit
 */
public class EmbeddedLdapTest {
	public static final String DOMAIN_DSN = "dc=example,dc=com";
	@Rule
	public EmbeddedLdapRule embeddedLdapRule = EmbeddedLdapRuleBuilder.newInstance().usingDomainDsn(DOMAIN_DSN)
			.importingLdifs("users-import.ldif").build();

	@Test
	public void shouldFindAllPersons() throws Exception {
		final LDAPInterface ldapConnection = embeddedLdapRule.ldapConnection();
		final SearchResult searchResult = ldapConnection.search(DOMAIN_DSN, SearchScope.SUB, "(objectClass=person)");
		assertThat(3, equalTo(searchResult.getEntryCount()));
		List<SearchResultEntry> searchEntries = searchResult.getSearchEntries();
		assertThat(searchEntries.get(0).getAttribute("cn").getValue(), equalTo("John Steinbeck"));
		assertThat(searchEntries.get(1).getAttribute("cn").getValue(), equalTo("Micha Kops"));
		assertThat(searchEntries.get(2).getAttribute("cn").getValue(), equalTo("Santa Claus"));
	}

	@Test
	public void shouldFindExactPerson() throws Exception {
		final LDAPInterface ldapConnection = embeddedLdapRule.ldapConnection();
		final SearchResult searchResult = ldapConnection.search("cn=Santa Claus,ou=Users,dc=example,dc=com",
				SearchScope.SUB, "(objectClass=person)");
		assertThat(1, equalTo(searchResult.getEntryCount()));
		assertThat(searchResult.getSearchEntries().get(0).getAttribute("cn").getValue(), equalTo("Santa Claus"));
	}
}

Running the Test

We may run our tests now using our IDE of choice or via Maven in the command line.

junit runner embedded ldap junit
Figure 2. JUnit Runner running embedded-ldap-junit test
$ mvn -Dtest=EmbeddedLdapTest test
[..]
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.EmbeddedLdapTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.588 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.016s

ApacheDS

ApacheDS is an extensible and embeddable, LDAPv3 compatible directory server.

In addition to the server, testing tools are also provided and there’s a detailed documentation available how to write tests using ApacheDS.

Dependencies

We need some more dependencies here and we’re adding some exclusions to avoid classpath conflicts with additional LDIF schema files.

<dependency>
	<groupId>org.apache.directory.server</groupId>
	<artifactId>apacheds-all</artifactId>
	<version>2.0.0-M22</version>
	<exclusions>
		<!-- exclude additional LDIF schema files to avoid conflicts through
			multiple copies -->
		<exclusion>
			<groupId>org.apache.directory.shared</groupId>
			<artifactId>shared-ldap-schema</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.apache.directory.api</groupId>
			<artifactId>api-ldap-schema-data</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.apache.directory.server</groupId>
	<artifactId>apacheds-server-integ</artifactId>
	<version>2.0.0-M22</version>
	<exclusions>
		<!-- exclude additional LDIF schema files to avoid conflicts through
			multiple copies -->
		<exclusion>
			<groupId>org.apache.directory.shared</groupId>
			<artifactId>shared-ldap-schema</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.apache.directory.api</groupId>
			<artifactId>api-ldap-schema-data</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.apache.directory.server</groupId>
	<artifactId>apacheds-core-integ</artifactId>
	<version>2.0.0-M22</version>
	<exclusions>
		<!-- exclude additional LDIF schema files to avoid conflicts through
			multiple copies -->
		<exclusion>
			<groupId>org.apache.directory.shared</groupId>
			<artifactId>shared-ldap-schema</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.apache.directory.api</groupId>
			<artifactId>api-ldap-schema-data</artifactId>
		</exclusion>
	</exclusions>
</dependency>

Integration Test

This is our final integration test. We may configure our server or multiple servers using annotations on class and on method level.

package it;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

import javax.naming.NamingEnumeration;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.SortControl;

import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.server.integ.ServerIntegrationUtils;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Test using apacheds
 */
@RunWith(FrameworkRunner.class)
@CreateLdapServer(transports = { @CreateTransport(protocol = "LDAP") })
@CreateDS(allowAnonAccess = true, partitions = {
		@CreatePartition(name = "Example Partition", suffix = "dc=example,dc=com") })
@ApplyLdifFiles("users-import.ldif")
public class ApacheDsLdapTest extends AbstractLdapTestUnit {

	@Test
	public void shouldFindAllPersons() throws Exception {
		LdapContext ctx = (LdapContext) ServerIntegrationUtils.getWiredContext(ldapServer, null)
				.lookup("ou=Users,dc=example,dc=com");

		// we want a sorted result, based on the canonical name
		ctx.setRequestControls(new Control[] { new SortControl("cn", Control.CRITICAL) });

		NamingEnumeration<SearchResult> res = ctx.search("", "(objectClass=person)", new SearchControls());
		assertThat(res.hasMore(), equalTo(true));

		assertThat(res.next().getName(), equalTo("cn=John Steinbeck"));
		assertThat(res.next().getName(), equalTo("cn=Micha Kops"));
		assertThat(res.next().getName(), equalTo("cn=Santa Claus"));

	}
}

Running the Test

We may run our tests now using our IDE of choice or via Maven in the command line.

junit runner apacheds test
Figure 3. JUnit Runner running ApacheDS tests
$ mvn -Dtest=ApacheDsLdapTest test
[..]
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.ApacheDsLdapTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.719 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.173s

Tutorial Sources

Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:

git clone https://github.com/hascode/ldap-junit-testing-tutorial.git

Setting up an ApacheDS Server

I have written a tutorial how to set up an ApacheDS server for testing in no time in my tutorial "Creating a LDAP server for your development environment in 5 minutes" – please feel free to read.

Article Updates

  • 2017-06-19: Typo in repository links fixed.