Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring weirdly depends on class location

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 {
like image 254
munHunger Avatar asked Dec 12 '19 11:12

munHunger


1 Answers

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.

like image 56
TheJeff Avatar answered Oct 23 '22 09:10

TheJeff