jdaugherty commented on code in PR #16399:
URL: https://github.com/apache/grails-core/pull/16399#discussion_r4101957057


##########
grails-core/src/main/groovy/org/grails/compiler/injection/GlobalGrailsClassInjectorTransformation.groovy:
##########
@@ -387,6 +391,8 @@ class GlobalGrailsClassInjectorTransformation implements 
ASTTransformation, Comp
         if (beansProperty == null || 
!classNode.getAnnotations(GRAILS_BEANS_ANNOTATION).isEmpty()) {

Review Comment:
   A `@Shared` block is dropped here without a word. Spock renames a shared 
field to `$spock_sharedField_beans` and removes the `beans` property, so 
`getProperty('beans')` is null by the time this runs:
   
   ```groovy
   class SharedBeansSpec extends Specification implements GrailsUnitTest {
   
       @Shared
       def beans = {
           bean('greeting', String) { 'hello' }
       }
   
       void "the bean is registered"() {
           expect:
           applicationContext.containsBean('greeting') // false, and no 
BeansConfiguration is generated
       }
   }
   ```
   
   With `@GrailsBeans` written out it fails instead, with `@GrailsBeans 
requires a 'beans' property initialised to a closure`, which points away from 
the cause. `@Shared` is a natural thing to reach for, since the context is 
built once per spec class. Could a unit test's shared `beans` field either be 
compiled like an instance one, or be reported with a message that names 
`@Shared`? A spec for whichever it is would cover it.



##########
grails-testing-support-core/src/test/resources/META-INF/grails-plugin.xml:
##########
@@ -0,0 +1,3 @@
+<plugin name='includedBeans' version='1.0'>

Review Comment:
   This file looks unnecessary: the global transform already writes 
`META-INF/grails-plugin.xml` for `IncludedBeansGrailsPlugin` into 
`build/classes/groovy/test`, since the descriptor is compiled from test 
sources. With this resource deleted, all of the module's tests still pass, the 
three included-plugin specs among them. Keeping it puts two descriptors for 
`includedBeans` on the test classpath (versions `1.0` and `8.0.0-SNAPSHOT`). 
Can it be removed?



##########
grails-testing-support-core/src/main/groovy/org/grails/testing/GrailsApplicationBuilder.groovy:
##########
@@ -170,9 +178,18 @@ class GrailsApplicationBuilder {
         beanFactory.allowCircularReferences = 
environment.getProperty(Settings.SPRING_MAIN_ALLOW_CIRCULAR_REFERENCES, 
Boolean, Boolean.TRUE)
 
         def classLoader = this.class.classLoader
+        // The test's own configuration first, as an application's comes 
before auto-configuration:
+        // parsed ahead of them, its beans are what an auto-configuration's 
@ConditionalOnMissingBean sees.
+        configurationClasses?.each { Class<?> configurationClass ->

Review Comment:
   Registering the test's configuration here means it is parsed before anything 
`TestRuntimeGrailsApplicationPostProcessor` writes. The harness defaults from 
`registerBeans` (`messageSource`, `proxyHandler`, `conversionService`, ...) and 
the included plugins' `doWithSpring` and `beanRegistrar` beans all land 
afterwards and win a shared name, so a `beans` block cannot replace any of them:
   
   ```groovy
   class MessageSourceSpec extends Specification implements GrailsUnitTest {
   
       def beans = {
           bean('messageSource', MyMessageSource)
       }
   
       void "the test's messageSource is used"() {
           expect:
           applicationContext.getBean('messageSource') instanceof 
MyMessageSource // fails: StaticMessageSource
       }
   }
   ```
   
   The same override through the deprecated `doWithSpring()` works, because it 
runs after `registerBeans` in the same closure. A nested `@Configuration` class 
behaves like the `beans` block, and a bean from an included plugin's 
`doWithSpring` or `beanRegistrar` (core's `customEditors`, say) replaces the 
test's in the same way. Only the test's `beanRegistrar()` gets the last word.
   
   That is the reverse of an application: 
`GrailsEarlyPluginRegistrationPostProcessor` drains plugin beans before the 
application's configuration is parsed, so an application's `beans` block 
replaces a plugin bean, as the upgrade guide says. The unit testing guide leads 
with the `beans` block for exactly this case ("To provide or replace beans in 
the context..."), so a test migrating off `doWithSpring` silently ends up with 
the default instead.
   
   Could the harness register the default and plugin beans ahead of the test's 
configuration, the way the early registration phase does for an application? If 
that is out of scope here, the guide should say that replacing a framework or 
plugin bean needs `beanRegistrar()`. Either way, a spec that replaces 
`messageSource` and an included plugin's bean from a `beans` block would pin 
whichever behavior is chosen.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to