Skip to content

Adding @ModuleSlicing to a @DataJpaTest annotated test causes non persistence related beans to be included in the module slice #1681

@MartinAhrer

Description

@MartinAhrer

I have a working test for testing a JPA repository that is annotated with @DataJpaTest.
The test only instantiates beans of the persistence slice, no controllers, no services.

After adding @ModuleSlicing(extraIncludes = ["integrationtest"]) the test suddenly creates beans from the service and web layer such as @Controller or @Service annotated classes.
This is using latest modulith and boot RC

spring-boot = "4.1.0-RC1"
spring-modulith-bom = "2.1.0-RC1"

I have some simpler modules with less dependencies to other modules where @ModuleSlicing perfectly works, but it is not working with complex ones that have a larger depth of module dependencies.

I know, the following is quite a big mouthful, hard to digest. For now I'm only asking for support on how to further diagnose the parts of Spring Modulith that customize the @DataJpaTest's annotation effects for narrowing bean dependencies to only the tested module.

So far I have not been able to create an example that has the same issue, nor to find a proper way of debugging that.

Here is some of the involved code first, followed by some of the log output that gives overview of the modules found.

package at.customer.app.delivery.internal

@ModuleSlicing(extraIncludes = ["integrationtest"])
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@TestPropertySource(properties = """
    spring.jpa.show-sql=true
    spring.modulith.runtime.flyway-enabled=true
""")
@Import([IntegrationTestConfiguration, DeliveryFactoryTestFixturesConfiguration])
class EnvelopeRepositorySpec extends AbstractAggregateRootRepositorySpecification<Envelope, EnvelopeIdentifier> {

    @Autowired
    EnvelopeFactory entityFactory

    @Autowired
    EnvelopeRepository repository

}

The exception below shows that for some reason a controller is instantiated even though a @DataJpaTest annotation is present.

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'envelopeController' defined in file [/Users/martinahrer/Development/wertekassa/wertekassa-backend/build/classes/java/main/at/dig/wertekassa/delivery/EnvelopeController.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'envelopeService' defined in file [/Users/martinahrer/Development/wertekassa/wertekassa-backend/build/classes/java/main/at/dig/wertekassa/delivery/EnvelopeService.class]: Unsatisfied dependency expressed through constructor parameter 2: No qualifying bean of type 'at.dig.wertekassa.order.OrderService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1382)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1221)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)

The test is importing 2 configurations that DO NOT USE component scanning but provide configuration for test containers and some test object factories. These imports are not needed as they mimic some of the @ModuleSlicing configuration by only including the vertical configurations of required modules. Removing them wont make it work

package at.customer.app.integrationtest

@Configuration
@Import([DatabaseTestcontainersConfiguration, PersistenceConfiguration])
class IntegrationTestConfiguration {
    @Bean
    AbstractAssociationAdapter associationAdapter() {
        new JpaAssociationAdapter()
    }
}
package at.customer.app.persistence;

@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "utcDateTimeProvider")
public class PersistenceConfiguration {
    @Bean
    public DateTimeProvider utcDateTimeProvider() {
        return () -> Optional.of(OffsetDateTime.now(ZoneOffset.UTC));
    }
}

package at.customer.app;

@Configuration(proxyBeanMethods = false)
public class DatabaseTestcontainersConfiguration {
    @Bean
    @ServiceConnection
    PostgreSQLContainer postgresContainer() {
        return new PostgreSQLContainer(DockerImageName.parse("postgres:latest"))
            .withReuse(true)
            .withUsername("spring")
            .withPassword("boot")
            .withDatabaseName("wertekassa");

    }
}

DeliveryFactoryTestFixturesConfiguration.groovy provides factories for the module and loads another module's factory configuration

package at.customer.app.delivery

@Configuration
@Import(OrderFactoryTestFixturesConfiguration)
class DeliveryFactoryTestFixturesConfiguration {

    @Bean
    EnvelopeFactory envelopeFactory(OrderFactory orderFactory, EnvelopePositionFactory envelopePositionFactory, AbstractAssociationAdapter associationAdapter) {
        new EnvelopeFactory(Optional.of(orderFactory), Optional.of(envelopePositionFactory), Optional.of(associationAdapter))
    }

   ...
}

Module delivery classes

└── at
    └── customer
        └── app
            ├── delivery
            │   ├── Address.java
            │   ├── AddressFieldConstraint.java
            │   ├── EmailAddress.java
            │   ├── Envelope.java
            │   ├── EnvelopeController.java                    <<<====
            │   ├── EnvelopeIdentifier.java
            │   ├── EnvelopeNumber.java
            │   ├── EnvelopeNumberConstraint.java
            │   ├── EnvelopePosition.java
            │   ├── EnvelopePositionIdentifier.java
            │   ├── EnvelopeService.java
            │   ├── internal
            │   │   ├── EnvelopeMapper.java
            │   │   ├── EnvelopePositionMapper.java
            │   │   ├── EnvelopePositionMapperDecorator.java
            │   │   ├── EnvelopeRepository.java                <<<====
            │   │   ├── package-info.java
            │   │   ├── StringToEnvelopeIdentifierConverter.java
            │   │   ├── StringToTransportCarrierIdentifierConverter.java
            │   │   ├── StringToTransportCompanyIdentifierConverter.java
            │   │   ├── TransportCarrierMapper.java
            │   │   ├── TransportCarrierRepository.java
            │   │   ├── TransportCompanyMapper.java
            │   │   └── TransportCompanyRepository.java
            │   ├── InvalidArticleQuantities.java
            │   ├── NameConstraint.java
            │   ├── package-info.java
            │   ├── TrackingNumber.java
            │   ├── TrackingNumberConstraint.java
            │   ├── TransportCarrier.java
            │   ├── TransportCarrierController.java
            │   ├── TransportCarrierIdentifier.java
            │   ├── TransportCarrierService.java
            │   ├── TransportCompany.java
            │   ├── TransportCompanyController.java
            │   ├── TransportCompanyIdentifier.java
            │   ├── TransportCompanyService.java
            │   └── workflow
            │       ├── DeliveryWorkflowGuardFactory.java
            │       └── package-info.java

integrationTest

└── at
    └── customer
        └── app
            ├── catalog
            │   ├── internal
            │   │   ├── ArticleRepositorySpec.groovy
            │   │   ├── MarketPriceRepositorySpec.groovy
            │   │   └── RicRateRepositorySpec.groovy
            │   └── JMoleculesIdentifierIntegrationSpec.groovy
            ├── delivery
            │   └── internal
            │       ├── EnvelopeRepositorySpec.groovy          <<<===
            │       ├── TransportCarrierRepositorySpec.groovy
            │       └── TransportCompanyRepositorySpec.groovy
            ├── integrationtest
            │   ├── IntegrationTestConfiguration.groovy
            │   ├── package-info.java
            │   └── persistence
            │       ├── AbstractAggregateRootRepositorySpecification.groovy
            │       ├── AbstractRepositorySpecification.groovy
            │       ├── JpaAssociationAdapter.groovy
            │       └── package-info.java
            ├── inventory
            │   └── internal
            │       ├── InventoryRepositorySpec.groovy
            │       └── TransactionRepositorySpec.groovy
            ├── notification
            │   └── internal
            │       ├── NotificationRepositorySpec.groovy
            │       └── TaskRepositorySpec.groovy
            ├── order
            │   ├── internal
            │   │   ├── OrderArticlePriceSnapshotRepositorySpec.groovy
            │   │   └── OrderRepositorySpec.groovy
            │   └── OrderServiceSpec.groovy
            ├── orderworkflow
            │   └── internal
            │       ├── OrderWorkflowRepositorySpec.groovy
            │       └── OrderWorkflowTransitionRepositorySpec.groovy
            ├── process
            │   └── internal
            │       └── ProcessTypeRepositorySpec.groovy
            ├── sample
            │   └── SampleControllerSpec.groovy
            └── security
                ├── AbstractKeycloakIntegrationSpec.groovy
                ├── KeycloakAdminServiceSpec.groovy
                └── KeycloakIntegration.groovy

The log showing the modules as detected by Spring Modulith

ustomizerFactory$ModuleContextCustomizer : Bootstrapping @org.springframework.modulith.test.ApplicationModuleTest for Delivery in mode STANDALONE (class at.customer.app.ServerApplication)…
ustomizerFactory$ModuleContextCustomizer : 
ustomizerFactory$ModuleContextCustomizer : # Delivery
ustomizerFactory$ModuleContextCustomizer : > Logical name: delivery
ustomizerFactory$ModuleContextCustomizer : > Base package: at.customer.app.delivery
ustomizerFactory$ModuleContextCustomizer : > Excluded packages: none
ustomizerFactory$ModuleContextCustomizer : > Named interfaces:
ustomizerFactory$ModuleContextCustomizer :   + NamedInterface: name=<<UNNAMED>>, types=[ a.d.w.d.Address, a.d.w.d.AddressFieldConstraint, a.d.w.d.DeliveryFactoryTestFixturesConfiguration, a.d.w.d.EmailAddress, a.d.w.d.Envelope, a.d.w.d.EnvelopeController, a.d.w.d.EnvelopeFactory, a.d.w.d.EnvelopeFactory._closure1, a.d.w.d.EnvelopeFactory._closure2, a.d.w.d.EnvelopeFactory._closure3, a.d.w.d.EnvelopeFactory._postProcessChildren_closure4, a.d.w.d.EnvelopeIdentifier, a.d.w.d.EnvelopeNumber, a.d.w.d.EnvelopeNumberConstraint, a.d.w.d.EnvelopePosition, a.d.w.d.EnvelopePositionFactory, a.d.w.d.EnvelopePositionFactory._closure1, a.d.w.d.EnvelopePositionFactory._closure2, a.d.w.d.EnvelopePositionIdentifier, a.d.w.d.EnvelopeService, a.d.w.d.InvalidArticleQuantities, a.d.w.d.NameConstraint, a.d.w.d.TrackingNumber, a.d.w.d.TrackingNumberConstraint, a.d.w.d.TransportCarrier, a.d.w.d.TransportCarrierController, a.d.w.d.TransportCarrierFactory, a.d.w.d.TransportCarrierFactory._closure1, a.d.w.d.TransportCarrierFactory._closure2, a.d.w.d.TransportCarrierIdentifier, a.d.w.d.TransportCarrierService, a.d.w.d.TransportCompany, a.d.w.d.TransportCompanyController, a.d.w.d.TransportCompanyFactory, a.d.w.d.TransportCompanyFactory._closure1, a.d.w.d.TransportCompanyIdentifier, a.d.w.d.TransportCompanyService ]
ustomizerFactory$ModuleContextCustomizer :   + NamedInterface: name=workflow, types=[ a.d.w.d.w.DeliveryWorkflowGuardFactory ]
ustomizerFactory$ModuleContextCustomizer : > Direct module dependencies: 
ustomizerFactory$ModuleContextCustomizer :   - catalog
ustomizerFactory$ModuleContextCustomizer :   - integrationtest
ustomizerFactory$ModuleContextCustomizer :   + order
ustomizerFactory$ModuleContextCustomizer :   - persistence
ustomizerFactory$ModuleContextCustomizer :   - service
ustomizerFactory$ModuleContextCustomizer :   - statemachine
ustomizerFactory$ModuleContextCustomizer :   + statespi
ustomizerFactory$ModuleContextCustomizer :   - test
ustomizerFactory$ModuleContextCustomizer :   - web
ustomizerFactory$ModuleContextCustomizer : > Spring beans:
ustomizerFactory$ModuleContextCustomizer :   + ….DeliveryFactoryTestFixturesConfiguration
ustomizerFactory$ModuleContextCustomizer :   + ….EnvelopeController
ustomizerFactory$ModuleContextCustomizer :   + ….EnvelopeFactory
ustomizerFactory$ModuleContextCustomizer :   + ….EnvelopePositionFactory
ustomizerFactory$ModuleContextCustomizer :   + ….EnvelopeService
ustomizerFactory$ModuleContextCustomizer :   + ….TransportCarrierController
ustomizerFactory$ModuleContextCustomizer :   + ….TransportCarrierFactory
ustomizerFactory$ModuleContextCustomizer :   + ….TransportCarrierService
ustomizerFactory$ModuleContextCustomizer :   + ….TransportCompanyController
ustomizerFactory$ModuleContextCustomizer :   + ….TransportCompanyFactory
ustomizerFactory$ModuleContextCustomizer :   + ….TransportCompanyService
ustomizerFactory$ModuleContextCustomizer :   o ….internal.EnvelopeMapperImpl
ustomizerFactory$ModuleContextCustomizer :   o ….internal.EnvelopePositionMapperImpl
ustomizerFactory$ModuleContextCustomizer :   o ….internal.EnvelopePositionMapperImpl_
ustomizerFactory$ModuleContextCustomizer :   o ….internal.EnvelopeRepository
ustomizerFactory$ModuleContextCustomizer :   o ….internal.StringToEnvelopeIdentifierConverter
ustomizerFactory$ModuleContextCustomizer :   o ….internal.StringToTransportCarrierIdentifierConverter
ustomizerFactory$ModuleContextCustomizer :   o ….internal.StringToTransportCompanyIdentifierConverter
ustomizerFactory$ModuleContextCustomizer :   o ….internal.TransportCarrierMapperImpl
ustomizerFactory$ModuleContextCustomizer :   o ….internal.TransportCarrierRepository
ustomizerFactory$ModuleContextCustomizer :   o ….internal.TransportCompanyMapperImpl
ustomizerFactory$ModuleContextCustomizer :   o ….internal.TransportCompanyRepository
ustomizerFactory$ModuleContextCustomizer :   + ….workflow.DeliveryWorkflowGuardFactory
ustomizerFactory$ModuleContextCustomizer : 
ustomizerFactory$ModuleContextCustomizer : Extra includes:
ustomizerFactory$ModuleContextCustomizer : > integrationtest
ustomizerFactory$ModuleContextCustomizer : 
ustomizerFactory$ModuleContextCustomizer : Shared modules:
ustomizerFactory$ModuleContextCustomizer : > web, persistence, service
ustomizerFactory$ModuleContextCustomizer : 
ustomizerFactory$ModuleContextCustomizer : Included dependencies:
ustomizerFactory$ModuleContextCustomizer : 
ustomizerFactory$ModuleContextCustomizer : # Integration Test Support
ustomizerFactory$ModuleContextCustomizer : > Logical name: integrationtest
ustomizerFactory$ModuleContextCustomizer : > Base package: at.customer.app.integrationtest
ustomizerFactory$ModuleContextCustomizer : > Excluded packages: none
ustomizerFactory$ModuleContextCustomizer : > Named interfaces:
ustomizerFactory$ModuleContextCustomizer :   + NamedInterface: name=<<UNNAMED>>, types=[ a.d.w.i.IntegrationTestConfiguration ]
ustomizerFactory$ModuleContextCustomizer :   + NamedInterface: name=persistence, types=[ a.d.w.i.p.AbstractAggregateRootRepositorySpecification, a.d.w.i.p.AbstractRepositorySpecification, a.d.w.i.p.AbstractRepositorySpecification.__spock_initializeFields_closure1, a.d.w.i.p.JpaAssociationAdapter ]
ustomizerFactory$ModuleContextCustomizer : > Direct module dependencies: 
ustomizerFactory$ModuleContextCustomizer :   - persistence
ustomizerFactory$ModuleContextCustomizer :   - test
ustomizerFactory$ModuleContextCustomizer : > Spring beans:
ustomizerFactory$ModuleContextCustomizer :   + ….IntegrationTestConfiguration
ustomizerFactory$ModuleContextCustomizer :   + ….persistence.JpaAssociationAdapter
ustomizerFactory$ModuleContextCustomizer :   o at.customer.app.test.AbstractAssociationAdapter
ustomizerFactory$ModuleContextCustomizer :   o at.customer.app.test.AssociationDetector
ustomizerFactory$ModuleContextCustomizer : 
ustomizerFactory$ModuleContextCustomizer : # Common Web Support
ustomizerFactory$ModuleContextCustomizer : > Logical name: web
ustomizerFactory$ModuleContextCustomizer : > Base package: at.customer.app.web
ustomizerFactory$ModuleContextCustomizer : > Excluded packages: none
ustomizerFactory$ModuleContextCustomizer : > Direct module dependencies: 
ustomizerFactory$ModuleContextCustomizer :   - service
ustomizerFactory$ModuleContextCustomizer : > Spring beans:
ustomizerFactory$ModuleContextCustomizer :   + ….DefaultControllerAdvice
ustomizerFactory$ModuleContextCustomizer :   o ….internal.JsonApiJMoleculesJacksonConfiguration
ustomizerFactory$ModuleContextCustomizer :   o ….internal.JsonMapperConfiguration
ustomizerFactory$ModuleContextCustomizer :   o com.toedter.spring.hateoas.jsonapi.JsonApiConfiguration
ustomizerFactory$ModuleContextCustomizer :   o org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer
ustomizerFactory$ModuleContextCustomizer : 
ustomizerFactory$ModuleContextCustomizer : # Common Persistence Support
ustomizerFactory$ModuleContextCustomizer : > Logical name: persistence
ustomizerFactory$ModuleContextCustomizer : > Base package: at.customer.app.persistence
ustomizerFactory$ModuleContextCustomizer : > Excluded packages: none
ustomizerFactory$ModuleContextCustomizer : > Direct module dependencies: none
ustomizerFactory$ModuleContextCustomizer : > Spring beans:
ustomizerFactory$ModuleContextCustomizer :   + ….PersistenceConfiguration
ustomizerFactory$ModuleContextCustomizer :   o ….internal.FlywayConfiguration
ustomizerFactory$ModuleContextCustomizer :   o org.springframework.boot.flyway.autoconfigure.FlywayMigrationStrategy
ustomizerFactory$ModuleContextCustomizer :   o org.springframework.data.auditing.DateTimeProvider
ustomizerFactory$ModuleContextCustomizer : 
ustomizerFactory$ModuleContextCustomizer : # Common Service Support
ustomizerFactory$ModuleContextCustomizer : > Logical name: service
ustomizerFactory$ModuleContextCustomizer : > Base package: at.customer.app.service
ustomizerFactory$ModuleContextCustomizer : > Excluded packages: none
ustomizerFactory$ModuleContextCustomizer : > Direct module dependencies: 
ustomizerFactory$ModuleContextCustomizer :   - persistence
ustomizerFactory$ModuleContextCustomizer : > Spring beans: none
ustomizerFactory$ModuleContextCustomizer : 

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions