8

I am trying to use Spring LDAP in one of my Spring Boot projects but I am getting an 'Address already in use' error when running multiple tests.

I have cloned locally the sample project here: https://spring.io/guides/gs/authenticating-ldap/

...and just added the boilerplate test normally created by Spring Boot to verify that the Application Context loads correctly:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyApplicationTests {
    @Test
    public void contextLoads() {
    }
}

If run alone, this test passes. As soon as LdapAuthenticationTests and MyApplicationTests are run together, I get the error above for the latter.

After debugging a bit, I've found out that this happens because the system tries to spawn a second instance of the embedded server.

I am sure I am missing something very stupid in the configuration. How can I fix this problem?

6 Answers6

8

I had a similar problem, and it looks like you had a static port configured (as was in my case).

According to this article:

Spring Boot starts an embedded LDAP server for each application context. Logically, that means, it starts an embedded LDAP server for each test class. Practically, this is not always true since Spring Boot caches and reuses application contexts. However, you should always expect that there is more than one LDAP server running while executing your tests. For this reason, you may not declare a port for your LDAP server. In this way, it will automatically uses a free port. Otherwise, your tests will fail with “Address already in use”

Thus it might be a better idea not to define spring.ldap.embedded.port at all.

AbstractVoid
  • 3,583
  • 2
  • 39
  • 39
  • Can you please show your ldap configuration methods? How do you set the port in non-testing environment. There is just testing part covered in the article. – Jan Cizmar Feb 27 '20 at 21:23
  • @JanCizmar The prod port is set in application.properties: `ldap.url=ldap://{domain}:{port}/DC={domaincomponent1},DC={domaincomponent2}` Security configuration is like (sorry for broken formatting): `auth.ldapAuthentication().userDetailsContextMapper(InetOrgPersonContextMapper()).userSearchBase({userSearchBaseConfig}).userSearchFilter({userSearchFilterConfig}).groupSearchBase({groupSearchBaseConfig}).contextSource().url({urlConfig}).managerDn({managerDnConfig}).managerPassword({managerPasswordConfig}) ` – AbstractVoid Feb 28 '20 at 14:37
  • 1
    Perfect!!! I just removed spring.ldap.embedded.port and it start working. – Parivesh Jain Apr 08 '20 at 21:42
7

I addressed the same issue. I solved it with an additional TestExecutionListener since you can get the InMemoryDirectoryServer bean.

/**
 * @author slemoine
 */
public class LdapExecutionListener implements TestExecutionListener {

    @Override
    public void afterTestClass(TestContext testContext) {
        InMemoryDirectoryServer ldapServer = testContext.getApplicationContext().getBean(InMemoryDirectoryServer.class);
        ldapServer.shutDown(true);
    }
}

And on each SpringBootTest (or only once in an abstract super class)

@RunWith(SpringRunner.class)
@SpringBootTest
@TestExecutionListeners(listeners = LdapExecutionListener.class,
        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
public class MyTestClass {

...

}

also do not forget

mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS

to avoid disabling the whole @SpringBootTest auto configuration.

slemoine
  • 367
  • 3
  • 8
  • 1
    This is should be the correct answer. I tried the above solutions and non of them worked for my case. Thank you for sharing this great solution. – Ramzi Guetiti Jun 18 '20 at 10:23
4

Okay, I think I found a solution by adding a @DirtiesContext annotation to my test classes:

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)

1

If you are using spring embedded ldap, try to comment or remove port value from config file as below :

spring :
  ldap:
    embedded:
      base-dn: dc=example,dc=org
      credential:
        username: cn=admin,dc=example,dc=org
        password: admin
      ldif: classpath:test-schema.ldif
      # port: 12345
      validation:
        enabled: false
Tomasz
  • 884
  • 8
  • 12
0

Try specifying the web environment type and the base configuration class (the one with !SpringBootApplication on it).

@RunWith(SpringRunner.class)
@SpringBootTest(
    classes = MyApplication.class,
    webEnvironment = RANDOM_PORT
)
public class MyApplicationTests {
    @Test
    public void contextLoads() {
    }
}

Do this for all your test classes.

Pytry
  • 6,044
  • 2
  • 37
  • 56
  • 1
    That doesn't fix the problem. I think it's because the ldap server is started every time an application context is loaded, which I guess is the case with SpringRunner? Not sure if manually tearing down on an @AfterClass-annotated method would fix the problem - even if it worked it would be annoying as every test class should have that... :-(... – Zaphod Beeblebrox Nov 14 '17 at 19:43
  • Ah yes, I missed that you were using embedded ldap (sorry). If you are using the default embedded ldap configuration and since the ldap port is not random, you would end up with problems if the embedded ldap is not cleaned before starting the next set of tests, but also if you run your tests in parallel. I would create a test configuration that sets a random port for ldap as well (like maybe the random web port+1). – Pytry Nov 15 '17 at 19:17
  • I tried that as well. Although it fixes the socket binding problem by getting a new port each time, I cannot find a valid configuration without using `.url()` - and if I need to pass a url, I need to pass a port as well (which I don't get to know as it's generated randomly). Although the docs say that the url should be passed only when not using the embedded server, it is used in the example they provide and I couldn't manage to make the app work without it. Also, by assigning a random port, does that mean a new embedded server is launched per test class? (which would be scary) – Zaphod Beeblebrox Nov 16 '17 at 22:29
0

I solved this problem by adding @DirtiesContext over each test class that requires embedded ldap server. In my case (and as I feel in many others), embedded ldap server was starting up at every @SpringBootTest, since I added all spring.ldap.embedded.* properties to general application-test.properties. Therefore, when I run a bunch of tests, the problem of 'Address already in use' broke all test passing.

Steps I followed:

  • create an additional test profile (with corresponding named application properties file, e.g. 'application-ldaptest.properties')

  • move to that file all spring.ldap.embedded.* properties (with fixed port value)

  • over all @SpringBootTest-s that do require embedded server running up, add @ActiveProfiles("testladp") and @DirtiesContext annotations.

Hope, that helps.

Ilona Pr
  • 1
  • 1