skip to Main Content

I am trying to do a simple test by calling an endpoint and asserting the response.
It is a Spring Reactive project where I have an r2dbc postgreSQL database.
The test is spinning up a testcontainer with a postgreSQL database.
I can put a breakpoint in the test and access the testcontainer db with PGAdmin and everything looks correctly initialized. I can also boot the application and have a local postgres image running and it works.

Some code to follow along

The repository is implemented as following:

public interface IOrganizerRepository extends ReactiveCrudRepository<Organizer, Long> {}

my application.properties file:

# Server
server.port=8090

# netty
reactor.netty.http.server.accessLogEnabled=true

# Database
spring.r2dbc.url=r2dbc:postgresql://127.0.0.1:8790/organizer_db
spring.r2dbc.username=user
spring.r2dbc.password=password

spring.flyway.user=${spring.r2dbc.username}
spring.flyway.password=${spring.r2dbc.password}
spring.flyway.url=jdbc:postgresql://127.0.0.1:8790/organizer_db

I have a jdbc dependency in the pom.xml to be able to have schema versioning via flyway( which also works when booting and when connecting to testcontainer db with PGAdmin)

Abstract class that spins up the testcontainer(have tried to add the @Container and PostgreSQLContainer inside the test class ) and test class:

public abstract class AbstractIT {

  private static final PostgreSQLContainer<?> postgres;

  static {
    postgres = new PostgreSQLContainer<>(DockerImageName.parse("postgres:14.5"));
    postgres.start();
  }

  @DynamicPropertySource
  static void properties(DynamicPropertyRegistry registry) {
    registry.add("spring.r2dbc.url",
        () -> String.format("r2dbc:postgresql://%s:%s/%s",
            postgres.getHost(),
            postgres.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT),
            postgres.getDatabaseName()));
    registry.add("spring.r2dbc.username", postgres::getUsername);
    registry.add("spring.r2dbc.password", postgres::getPassword);
    registry.add("spring.flyway.url", postgres::getJdbcUrl);
  }
}
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT, classes = OrganizerApplication.class)
@TestPropertySource(locations = "classpath:applicationtest.properties")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
class SpringDbTest extends AbstractIT {

  @Autowired
  private WebTestClient webTestClient;

  @Test
  void test_test() {
    OrganizerDTO org = webTestClient
        .get().uri("/v1/admin/organizers/2")
        .exchange()
        .expectStatus().is2xxSuccessful()
        .expectBody(OrganizerDTO.class).returnResult().getResponseBody();
    Assertions.assertNotNull(org.id());
    System.out.println(org);
    Assertions.assertEquals(2L, org.id());
  }
}

Error when executing the test:
org.springframework.dao.DataAccessResourceFailureException: Failed to obtain R2DBC Connection; nested exception is io.r2dbc.postgresql.PostgresqlConnectionFactory$PostgresConnectionException: Cannot connect to localhost/<unresolved>:8790

Testcontainer has created a postgres DB with the following properties

url=r2dbc:postgresql://localhost:63696/test # Different port every time
username=test
password=test

I have tested to print the values from application properties by injecting them with @Value and can see that they have been overwritten in runtime

Conclusions
Somehow everything seem to work except that the repository is not connecting to the changed properties from the @DynamicPropertySource and is still trying to connect to the properties set in application.properties. There must be some timing issue when the crud repository initializes the Databaseclient to when the properties are set with the help of DynamicProperties.
I have seen examples using the same set up where it is working, but I might be missing some configuration.

I have been trying to find a way to set the order of initialization of the repo with no luck.

Thanks for the help!

2

Answers


  1. The reason why you are getting localhost/<unresolved>:8790 which contains the port declared in the properties file and not the random port provided by testcontainers is because the spring.r2dbc.url property is not being set by AbstractIT. In order to fix it, move @DynamicPropertySource static void properties to SpringDbTest test class.

    Also, you may not need @ExtendWith(SpringExtension.class) if it is already declared by @SpringBootTest. I recommend to set spring.flyway.user and spring.flyway.password in properties method too so you may not need TestPropertySource and DirtiesContext

    Login or Signup to reply.
  2. I had the same problem as you and I solved it like this

    code

    add @ContextConfiguration(initializers = BaseMapperTest.MyTiDBContainer.class)

    @ExtendWith(SpringExtension.class)
    @MybatisTest
    @MapperScan(basePackages = {"xxx.xxx.xxx.**.dao"})
    @Sql(scripts = "/create.sql")
    @Sql(scripts = "/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    @Slf4j
    @Testcontainers
    @ContextConfiguration(initializers = BaseMapperTest.MyTiDBContainer.class) 
    public class BaseMapperTest<M, T> {
        @Container
        protected TiDBContainer tidb = initTiDBContainer();
        @Autowired
        protected M mapper;
    
        private TiDBContainer initTiDBContainer() {
            try (TiDBContainer tiDBContainer = new MyTiDBContainer()) {
                return tiDBContainer
                        .withReuse(false)
                        .withUrlParam("allowMultiQueries", "true"); // 解决 client has multi-statement capability disabled
            }
        }
        public static class MyTiDBContainer extends TiDBContainer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
            ...
    
            @Override
            public void initialize(ConfigurableApplicationContext context) {
                this.start();
                TestPropertyValues.of(
                        "spring.datasource.username=" + this.getUsername(),
                        "spring.datasource.password=" + this.getPassword(),
                        "spring.datasource.url=" + this.getJdbcUrl()
                ).applyTo(context.getEnvironment());
            }
        }
    }
    
    class SensorMapperTest extends BaseMapperTest<SensorMapper, VehicleSensor> {
        @Test
        void checkExist() {
            ...
        }
    }
    

    another way

    @ExtendWith(SpringExtension.class)
    @MybatisTest
    @MapperScan(basePackages = {"xxx.**.dao"})
    @Sql(scripts = "/create.sql")
    @Sql(scripts = "/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    @Slf4j
    @Testcontainers
    public class BaseMapperTest<M, T> {
        @Container
        protected TiDBContainer tidb = initTiDBContainer();
    
        @DynamicPropertySource
        static void dps(DynamicPropertyRegistry registry) throws InterruptedException {
            BaseMapperTest<?, ?> baseMapperTest = new BaseMapperTest<>();
            baseMapperTest.tidb.start();
            while (!baseMapperTest.tidb.isRunning()) {
    // maybe no into this
                log.info("wait for tidb container running...");
                TimeUnit.SECONDS.sleep(1);
            }
            baseMapperTest.config(registry);
        }
    
        private void config(DynamicPropertyRegistry registry) {
            registry.add("spring.datasource.username", tidb::getUsername);
            registry.add("spring.datasource.password", tidb::getPassword);
            registry.add("spring.datasource.url", tidb::getJdbcUrl);
        }
    
    
        @Autowired
        protected M mapper;
    
        protected TiDBContainer initTiDBContainer() {
            log.info("init db...");
            ...
        }
        
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search