Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testcontainers does not configure the test with matching containers #1247

Open
MaartenAerts opened this issue Jul 16, 2023 · 10 comments
Open

Comments

@MaartenAerts
Copy link

Using spring initializr with following configuration
https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.1.1&packaging=jar&jvmVersion=17&groupId=com.demo&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.demo.demo&dependencies=lombok,devtools,docker-compose,web,data-jpa,validation,flyway,postgresql,testcontainers,restdocs

Following error occurs during the provided contextLoads() test

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:171)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655)
	... 117 more
Caused by: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine a suitable driver class
	at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.determineDriverClassName(DataSourceProperties.java:186)
	at org.springframework.boot.autoconfigure.jdbc.PropertiesJdbcConnectionDetails.getDriverClassName(PropertiesJdbcConnectionDetails.java:49)
	at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.createDataSource(DataSourceConfiguration.java:55)
	at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari.dataSource(DataSourceConfiguration.java:117)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139)
	... 118 more

This issue can be solved by importing the example TestConfiguration in the test class.
Should this be done by default?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 16, 2023
@snicoll snicoll changed the title Failed to determine a suitable driver straight out of spring initializr with testcontainers Testcontainers does not configure the test with matching containers Jul 17, 2023
@snicoll snicoll added type: enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jul 17, 2023
@CamilYed

This comment was marked as resolved.

@simasch
Copy link

simasch commented Jan 9, 2024

It would be beneficial to separate the configuration from the application.

That's what is generated.

@TestConfiguration(proxyBeanMethods = false)
public class TestDemoApplication {

    @Bean
    @ServiceConnection
    PostgreSQLContainer<?> postgresContainer() {
        return new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest"));
    }

    public static void main(String[] args) {
        SpringApplication.from(DemoApplication::main).with(TestDemoApplication.class).run(args);
    }

}

But it would be better to get how the example in the reference documentation looks like (https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/#features.testcontainers.at-development-time)

For example:

@TestConfiguration(proxyBeanMethods = false)
public class TestDemoConfiguration {

    @Bean
    @ServiceConnection
    PostgreSQLContainer<?> postgresContainer() {
        return new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest"));
    }

}

@Import(TestDemoConfiguration.class)
public class TestDemoApplication {

    public static void main(String[] args) {
        SpringApplication.from(DemoApplication::main).with(TestDemoConfiguration.class).run(args);
    }

}

Because especially for new Spring Boot developers it may look strange to import an application class in test classes.

@snicoll
Copy link
Contributor

snicoll commented Jan 9, 2024

Because especially for new Spring Boot developers it may look strange to import an application class in test classes.

I am not following the argument, nor why the example you've shared doesn't "import an application class". I am also assuming that your example having two classes with the same name is a mistake on your end.

@simasch
Copy link

simasch commented Jan 9, 2024

I'm sorry @snicoll but, I thought it might be clear from the issue description.

The issue describes that the generated Test doesn't work because it has no database connection:

@SpringBootTest
class DemoApplicationTests {

    @Test
    void contextLoads() {
    }

}

To overcome this problem you can import the application class:

@Import(TestDemoApplication.class)
@SpringBootTest
class DemoApplicationTests {

    @Test
    void contextLoads() {
    }

}

My comment is that it looks silly to import an Application and therefore it would be better to separate configuration and application.

@snicoll
Copy link
Contributor

snicoll commented Jan 9, 2024

I'm sorry @snicoll but, I thought it might be clear from the issue description.

I'd say that a way to make it clear is to share an example of what you actually mean, rather than relying on context. That last snippet is much better IMO.

My comment is that it looks silly to import an Application

OK but we're not even there yet. We'll revisit this based on whatever we decide doing.

@simasch

This comment was marked as off-topic.

@tedyoung
Copy link

I and others have run into this issue: it's unexpected to have a test fail "out of the box". While a better solution is being worked on, documenting this behavior somewhere would save a lot of troubleshooting effort. Perhaps this could be a comment in the generated test class?

@stephaneeybert
Copy link

Being on SpringBoot 3.3.0 and having added the explicit @Import annotation, running the test still gives the Failed to determine a suitable driver class error message:

@mhalbritter
Copy link
Contributor

mhalbritter commented Jun 5, 2024

My suggestion would be: generate a dedicated test configuration class for the testcontainers:

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {
    @Bean
    @ServiceConnection
    PostgreSQLContainer<?> postgresContainer() {
        return new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest"));
    }
}

The class which holds the test-main-method can then simplified to this:

public class TestSt1247Application {

	public static void main(String[] args) {
		SpringApplication.from(St1247Application::main).with(TestcontainersConfiguration.class).run(args);
	}

}

and the test class has to be augmented with an @Import, like this:

@SpringBootTest
@Import(TestcontainersConfiguration.class)
class St1247ApplicationTests {

	@Test
	void contextLoads() {
	}

}

With that, we don't import the "application" class, spring-boot:test-run works and the tests pass, too.

@stephaneeybert
Copy link

I could successfully run the tests with the following setup.

The test class itself:

@Testcontainers
@Import(TestContainerConfig.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PostControllerTest {

    private static final String API_ROOT = "/api/posts";

    @Autowired
    MariaDBContainer<?> mariaDBContainer;

    @Autowired
    PostgreSQLContainer<?> postgreSQLContainer;

    @Autowired
    PostRepository postRepository;

    @Autowired
    RestClient restClient;

    @LocalServerPort
    private int port;
    private String uriBase;

    @BeforeEach
    public void setup() {
        uriBase = "http://localhost:" + port;
    }

    @Test
    void shouldFindAll() {
        Post[] posts = restClient.get()
                .uri(uriBase + API_ROOT)
                .retrieve()
                .body(Post[].class);

        assertThat(posts.length).isEqualTo(100);
    }

The containers configuration:

@Component
public class TestContainerConfig {

    @Bean(destroyMethod = "stop")
    @ServiceConnection
    public MariaDBContainer<?> mariaDBContainer() {
        return new MariaDBContainer<>("mariadb:10.5.5")
                .withDatabaseName("posts")
                .withReuse(true);
    }

    @Bean(destroyMethod = "stop")
    @ServiceConnection
    public PostgreSQLContainer<?> postgreSQLContainer() {
        return new PostgreSQLContainer<>("postgres:16.0")
                .withDatabaseName("posts")
                .withReuse(true);
    }
}

For the repository tests I used a base class even if it was not needed:

class PostRepositoryTest extends BaseTest {

    @Test
    void mariadbConnectionEstablished() {
        assertThat(mariaDBContainer.isCreated()).isTrue();
        assertThat(mariaDBContainer.isRunning()).isTrue();
    }

    @Test
    void postgresqlConnectionEstablished() {
        assertThat(postgreSQLContainer.isCreated()).isTrue();
        assertThat(postgreSQLContainer.isRunning()).isTrue();
    }

    @BeforeEach
    void setup() {
        List<Post> posts = List.of(new Post(1, 1, "Hi", "The hi post", null));
        postRepository.saveAll(posts);
    }

    @Test
    void shouldReturnPostByTitle() {
        Post post = postRepository.findByTitle("Hi");
        assertThat(post).isNotNull();
    }
}

@Testcontainers
@DataJdbcTest
@Import(TestContainerConfig.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
abstract class BaseTest {

    @Autowired
    MariaDBContainer<?> mariaDBContainer;

    @Autowired
    PostgreSQLContainer<?> postgreSQLContainer;

    @Autowired
    PostRepository postRepository;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants