zonkyio / embedded-database-spring-test

A library for creating isolated embedded databases for Spring-powered integration tests.
Apache License 2.0
412 stars 37 forks source link

Problem with two data sources #236

Closed peterlitvak closed 1 year ago

peterlitvak commented 1 year ago

I have two data sources configured for my test like so:

@AutoConfigureEmbeddedDatabase(beanName = "readOnlyDataSource",
                               type = AutoConfigureEmbeddedDatabase.DatabaseType.POSTGRES,
                               provider = AutoConfigureEmbeddedDatabase.DatabaseProvider.DOCKER,
                               refresh = AutoConfigureEmbeddedDatabase.RefreshMode.BEFORE_EACH_TEST_METHOD)
@AutoConfigureEmbeddedDatabase(beanName = "readWriteDataSource",
                               type = AutoConfigureEmbeddedDatabase.DatabaseType.POSTGRES,
                               provider = AutoConfigureEmbeddedDatabase.DatabaseProvider.DOCKER,
                               refresh = AutoConfigureEmbeddedDatabase.RefreshMode.BEFORE_EACH_TEST_METHOD)

When I execute tests, the queries against readWriteDataSource are executing fine but fail with "relation xyz does not exist" errors on the readOnlyDataSource. What could be the problem here?

tomix26 commented 1 year ago

I've already explained it here in detail. Below is the solution for your specific case.

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureEmbeddedDatabase(beanName = "readWriteDataSource")
public class MultipleDataSourcesTest {

    @TestConfiguration // or @Configuration
    static class Config {

        @Bean
        public DataSource readOnlyDataSource(DataSource readWriteDataSource) {
            ReadOnlyDataSourceAdapter adapter = new ReadOnlyDataSourceAdapter();
            adapter.setTargetDataSource(readWriteDataSource);
            return adapter;
        }
    }

    public static class ReadOnlyDataSourceAdapter extends DelegatingDataSource {

        @Override
        public Connection getConnection() throws SQLException {
            Connection connection = super.getConnection();
            connection.setReadOnly(true);
            return connection;
        }

        @Override
        public Connection getConnection(String username, String password) throws SQLException {
            Connection connection = super.getConnection(username, password);
            connection.setReadOnly(true);
            return connection;
        }
    }

    @Autowired
    @Qualifier("readWriteDataSource")
    private DataSource readWriteDataSource;

    @Autowired
    @Qualifier("readOnlyDataSource")
    private DataSource readOnlyDataSource;

    // class body...
}
peterlitvak commented 1 year ago

Thank you! I ended up configuring tests to use actual data sources (defined in the app) connecting to the embedded Postgres. I used one of the tests you have as an example. It seems to work as expected.

Is this indeed the right way to do this:

public class EmbeddedPostgresInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    public static PostgreSQLContainer<?> postgresContainer = new PostgreSQLContainer<>("postgres:11-alpine");

    static {
        postgresContainer.start();
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

        applicationContext.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
            "some_name", Map.of(
            "datasource.rw.url", postgresContainer.getJdbcUrl(),
            "datasource.rw.username", postgresContainer.getUsername(),
            "datasource.rw.password", postgresContainer.getPassword(),
            "datasource.ro.url", postgresContainer.getJdbcUrl(),
            "datasource.ro.username", postgresContainer.getUsername(),
            "datasource.ro.password", postgresContainer.getPassword()
        )));
    }
}

in the test:

@ExtendWith(SpringExtension.class)
@SpringBootTest
@ContextConfiguration(classes = MyTestConfiguration.class,
                      initializers = EmbeddedPostgresInitializer.class)
@AutoConfigureEmbeddedDatabase(type = POSTGRES, replace = NONE, refresh = AFTER_EACH_TEST_METHOD)
public class MyIntegTest {
...
}
tomix26 commented 1 year ago

Yes, that certainly looks functional. However, you don't actually use this library at all anymore, because the container you are starting this way is out of the control of this library. Which results in refresh = AFTER_EACH_TEST_METHOD having no effect.

peterlitvak commented 1 year ago

Will it still pull the container? Is there a better way to preserve the original data sources but have the library do its thing?

tomix26 commented 1 year ago

Will it still pull the container?

As I wrote, it will work fine. You just don't use this library and you will lose the benefits it brings.

Is there a better way to preserve the original data sources but have the library do its thing?

The library automatically replaces the original data sources with the testing ones. In general, this should not be a problem, because in most cases the app code refers to the DataSource interface and in tests you rarely care about the specific implementation (e.g. HikariCP). However, no, the library does not allow the use of any specific connection pool in tests.

peterlitvak commented 1 year ago

Thank you for the explanation!

tomix26 commented 1 year ago

I'm closing this issue is favor of #234, which seems to be similar.