zonkyio / embedded-database-spring-test

A library for creating isolated embedded databases for Spring-powered integration tests.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Connection to localhost:5432 refused during application startup before unit test execution

bobbyrne01 opened this issue · comments

By default my application will acquire db details from application.properties on app start-up, which is part of Hibernate's startup processing.

# Primary Spring DataSource configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=password

I'm wondering what host:port should be set in the application's properties in order for this library to handle connection requests like this? If it is even possible?

Configuration:

implementation("org.liquibase:liquibase-core:4.13.0")
implementation("org.liquibase:liquibase-gradle-plugin:2.0.4")
implementation("org.hibernate:hibernate-core:5.6.14.Final")
implementation("org.hibernate:hibernate-hikaricp")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.postgresql:postgresql")
testImplementation("io.zonky.test:embedded-database-spring-test:2.2.0")

Error:

2023-01-16 13:23:24.017  INFO 74329 --- [    Test worker] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-01-16 13:23:24.051  INFO 74329 --- [    Test worker] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.14.Final
2023-01-16 13:23:24.165  INFO 74329 --- [    Test worker] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2023-01-16 13:23:24.262  INFO 74329 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-01-16 13:23:25.326 ERROR 74329 --- [    Test worker] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Exception during pool initialization.

org.postgresql.util.PSQLException: Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
  at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:319) ~[postgresql-42.3.8.jar:42.3.8]
  at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:49) ~[postgresql-42.3.8.jar:42.3.8]
  at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:223) ~[postgresql-42.3.8.jar:42.3.8]
  at org.postgresql.Driver.makeConnection(Driver.java:402) ~[postgresql-42.3.8.jar:42.3.8]
  at org.postgresql.Driver.connect(Driver.java:261) ~[postgresql-42.3.8.jar:42.3.8]
  at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138) ~[HikariCP-4.0.3.jar:na]

The Java test:

import static io.zonky.test.db.AutoConfigureEmbeddedDatabase.DatabaseType.POSTGRES;
import static org.junit.jupiter.api.Assertions.fail;

import java.sql.*;
import java.util.Date;

import io.zonky.test.db.AutoConfigureEmbeddedDatabase;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.annotation.Transactional;

@ActiveProfiles("test")
@SpringBootTest
@AutoConfigureEmbeddedDatabase(type = POSTGRES)
public class MyServiceTest {

  @Autowired private MyService myService;

  @Test
  void test_Create() throws InterruptedException {

    String myText = "Initial value: " + new Date();
    MyDTO myDTO = new MyDTO();
    myDTO.setText(myText);

    myDto = myService.create(myDTO);

    Assertions.assertEquals(1, myService.count());
    Assertions.assertTrue(myDto.getId() > 0);
    Assertions.assertEquals(myText, myDTO.getText());
  }
}

Where the exception originates from:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.connections.spi.AbstractMultiTenantConnectionProvider;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.hikaricp.internal.HikariCPConnectionProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.stereotype.Component;

@Component
public class MultiTenantConnectionProviderImpl extends AbstractMultiTenantConnectionProvider {

  private final ConcurrentHashMap<String, HikariCPConnectionProvider> connectionPools =
      new ConcurrentHashMap<>();

  @Autowired private org.springframework.core.env.Environment springEnv;

  @Autowired private JpaProperties jpaProperties;

  @Override
  protected ConnectionProvider getAnyConnectionProvider() {
    return selectConnectionProvider("");
  }

  @Override
  protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {

    HikariCPConnectionProvider connectionProvider = connectionPools.get(tenantIdentifier);

    String jdbcUrl;
    String databaseUsername;
    String databasePassword;

    if (connectionProvider != null) {

      return connectionProvider;

    } else {

      if (tenantIdentifier.equalsIgnoreCase("")) {
        jdbcUrl = springEnv.getProperty("spring.datasource.url");
        databaseUsername = springEnv.getProperty("spring.datasource.username");
        databasePassword = springEnv.getProperty("spring.datasource.password");
      } else {

        jdbcUrl = springEnv.getProperty("jdbcurl");
        databaseUsername = springEnv.getProperty("username");
        databasePassword = springEnv.getProperty("database");
      }

      connectionProvider =
          initializeConnectionProvider(
              jdbcUrl, databaseUsername, databasePassword);
      connectionPools.put(tenantIdentifier, connectionProvider);
    }

    return connectionProvider;
  }

  
  private HikariCPConnectionProvider initializeConnectionProvider(
    String jdbcUrl, String databaseUsername, String databasePassword) {

    final Map<String, String> settings = new HashMap<>();
    settings.putAll(jpaProperties.getProperties());

    // Set required properties
    settings.put(Environment.URL, jdbcUrl);
    settings.put(Environment.USER, databaseUsername);
    settings.put(Environment.PASS, databasePassword);
    settings.put(Environment.CONNECTION_PROVIDER, HikariCPConnectionProvider.class.getName());

    final HikariCPConnectionProvider connectionProvider = new HikariCPConnectionProvider();
    connectionProvider.configure(settings); // <-- Exception here, based on URL

    return connectionProvider;
  }
}

Hi @bobbyrne01!

The library doesn't rely on the mentioned properties, it works at the level of data source beans. So you don't have to worry about your application.properties, it's fine as is. But what is a problem is the custom implementation of MultiTenantConnectionProvider. Because the implementation operates with spring.datasource.* variables instead of the data source beans. That's why a test data source cannot be correctly propagated to hibernate and maybe other components. So you have to get rid of the MultiTenantConnectionProviderImpl (at least for tests, using spring profile), or replace it with a test implementation referring to a data source. Check out the example below.

@Component
public class TestConnectionProvider extends AbstractMultiTenantConnectionProvider {

  @Autowired private DataSource dataSource;

  @Override
  protected ConnectionProvider getAnyConnectionProvider() {
    return selectConnectionProvider("");
  }

  @Override
  protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
    DatasourceConnectionProviderImpl connectionProvider = new DatasourceConnectionProviderImpl();
    connectionProvider.setDataSource(dataSource);
    return connectionProvider;
  }
}

Thanks @tomix26 I've used a test impl as you suggested and unit test is working now.

Appreciate the quick and helpful response!