6

I have a SpringBootTest test that should rely on a separate class to setup an embedded Postgres and datasource.

So the Repository configuration looks like this:

package com.stream.repository.configuration
@Configuration
@ComponentScan(basePackages = arrayOf("com.stream.repository"))
@EntityScan(basePackages = arrayOf("com.stream.repository"))
@EnableJpaRepositories(basePackages = arrayOf("com.stream.repository"))
@EnableAutoConfiguration
class RepositoryConfiguration {

And the test class looks like this:

package com.stream.webapp.rest
@AutoConfigureMockMvc(addFilters = false)
@SpringBootTest(properties =
[
    "spring.jpa.hibernate.ddl-auto=validate",
    "spring.jpa.show-sql=true",
    "spring.liquibase.enabled=true",
    "spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.yml",
    "spring.jpa.properties.hibernate.jdbc.time_zone=UTC"
],
        classes = [RepositoryConfiguration::class, AuditController::class],
        webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class AuditControllerTest {

And here is where it gets weird. If I run with that configuration it will complain about not finding an EntityManagerFactory

AuditService required a bean of type 'javax.persistence.EntityManagerFactory' that could not be found.

After a lot of messing around I found a solution to this problem. If I move the RepositoryConfiguration so that it is in the package com.stream.webapp.rest, i.e. the same as AuditControllerTest then it magically works.

I cannot seem to find any reason for why that is the case. So can anyone explain it and is there a way around it? because I don't want to move it. It makes a lot of sense to have it where it is.

As a side note, it is written in Kotlin, but I can't see why it would matter in this case. And this is only for testing. When running the application outside of a test scope, it works

I can also add that the AuditControllerTest is in one module and RepositoryConfiguration is in another. Not sure if it is relevant as it works if it is placed in the "right" package (still separate modules)

TL;DR of the question: Why does spring care that the RepositoryConfiguration is in the same package as AuditControllerTest ?

Update: This is the current configuration: (RepositoryConfiguration is unchanged

@AutoConfigureMockMvc(addFilters = false)
@ComponentScan("com.stream.repository")
@Configuration
@SpringBootTest(properties =
[
    "spring.jpa.hibernate.ddl-auto=validate",
    "spring.jpa.show-sql=true",
    "spring.liquibase.enabled=true",
    "spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.yml",
    "spring.jpa.properties.hibernate.jdbc.time_zone=UTC",
    "database.dbname=stream_mapper"
],
        classes = [com.stream.repository.configuration.RepositoryConfiguration::class, ExceptionMapper::class, AuditController::class],
        webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class AuditControllerTest {
munHunger
  • 2,572
  • 5
  • 34
  • 63
  • does it work if you add a @ComponentScan annotation in the test that references the com.stream.repository package? – David Dec 19 '19 at 12:03
  • sadly no. I tried adding `@ComponentScan` and `@ComponentScan("com.stream.repository")` to the `AuditControllerTest` class and I still get the error with not finding `EntityManagerFactory` – munHunger Dec 19 '19 at 13:12
  • how about with a @Configuration annotation as well? – David Dec 19 '19 at 15:07
  • looks like that isn't working either (with the same error). I'll update my post with the latest config – munHunger Dec 20 '19 at 08:59
  • It would be easier to identify the problem if you provided a minimal, reproducible example: https://stackoverflow.com/help/minimal-reproducible-example – Anar Sultanov Dec 20 '19 at 10:45
  • Have you tried leveraging the `@DataJpaTest` annotation to help you with configuring the test? https://reflectoring.io/spring-boot-data-jpa-test/ not saying this would solve your problem, but mostly a curiosity thing. – Elias Ranz Dec 21 '19 at 13:47
  • how about @Import(com.stream.repository.configuration.RepositoryConfiguration.class) - the classes entry seems to be there to indicate to Spring Boot which application class to use to create an application context – David Dec 23 '19 at 11:56

1 Answers1

1

Spring is weird about this, and will expect the package hierarchy to be equal or roughly equal for tests and the main project. Doing this is best practice and will save you a lot of headache later.

For configuring your Integration Test put something like "BaseIntegrationTest.java" or "AbstractIntegrationTest.java" in the same package hierarchy as your primary Spring Boot Configuration, substituting "main" with "test".

Ex:

Main config is here:

src/main/java/com/stream/repository/configuration/RepositoryConfiguration.java

Test config would be here (with @SpringBootTest)

src/test/java/com/stream/repository/configuration/TestRepositoryConfiguration.java

In the structure above, you'd put most (or all) of your Spring Boot Test Configuration Annotations on the base Test Configuration Class (TestRepositoryConfiguration.java)

Then your Actual Tests would extend this class:

Ex:

src/test/java/com/stream/webapp/rest/AuditControllerTest.java

Will start with:

class AuditControllerTest extends TestRepositoryConfiguration { ... }

Furthermore there are some odd things about the main RepositoryConfiguration.java class you posted - as it has the generic @EnableAutoConfiguration annotation on it, which shouldn't be required on a config specific to repositories - it is automatically included in the @SpringBootApplication annotation in your base webapp.

Regarding your Integration Test,

the @SpringBootTest should ideally not be on this file, even though most guides put that annotation on their example classes for simplicity. Ideally you'll want to put this on your base integration test file (often abstract) that is in the same structure (except with test rather than main) as your main application's base configuration file that defines the @SpringBootApplication - and keep a hierarchy of inheritance similar for test configuration and main configuration classes.

Here is a fantastic article about optimizing integration tests - I.E. keep one instance of the app running for all tests of the same type.

https://www.baeldung.com/spring-tests

Also, consider using @JdbcTest if you only need to test a repository. This can be kept as a separate context/type of test that doesn't extend from the main application test if you want to abstract repo-specific tests from your main integration tests.

Obviously I don't have access to your whole code-base so it will be hard for any suggestion I make to fully fix any problem, but hopefully this will get you thinking along the right lines of structurally setting up your integration tests to avoid a lot of odd build errors and resolution problems going forward. (And every dev who has to inherit your code hereafter)...

Comments/Questions welcome.

TheJeff
  • 3,665
  • 34
  • 52
  • 1
    I unfortunately don't have access to the code during my Christmas break, so I can't test this right now, but I think that this is a quite good answer! (can also add that I had a lot of issues trying to get a "minimal" code sample ready that doesn't break any NDAs :) ) – munHunger Dec 23 '19 at 15:31
  • Thanks for the bounty! If it helps, I usually start by making a TestApplication and using the main app's primary config, and start abstracting or separating from there depending on what needs to be customized for tests. The first answer here is the basic idea for a base test class: https://stackoverflow.com/questions/38469706/spring-boot-test-configuration Furthermore you can override beans for tests, etc with spring autoconfigure's conditionals in this test class or subclasses... I know, posting without putting company info in takes a lot of time! – TheJeff Dec 23 '19 at 15:58