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 :
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@Controlleror@Serviceannotated classes.This is using latest modulith and boot RC
I have some simpler modules with less dependencies to other modules where
@ModuleSlicingperfectly 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.
The exception below shows that for some reason a controller is instantiated even though a
@DataJpaTestannotation is present.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
DeliveryFactoryTestFixturesConfiguration.groovyprovides factories for the module and loads another module's factory configurationModule
deliveryclassesThe log showing the modules as detected by Spring Modulith