1

In a grails 2 project I'm using groovy's metaclass programming to add some methods to my domain classes.

Everything is working fine at runtime and I can run my integration tests fine.

But for unit tests I have some issues.

I have created a test mixin that is in charge of initializing the metaclass programming part.

This mixin is not running reliably: the methods added to the metaclass are not available, or they are available after a first call, or they are available only after a previous grails test-app unit: command has been called.
This is quite a problem for continuous build.

You should be able to reproduce this issue (at least with grails 2.0.4) by
0) create a new grails projects

1) add a domain object

create-domain-class playground.Data

2) add this class to your src/groovy/playground dir

    package playground

import grails.test.mixin.domain.DomainClassUnitTestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.codehaus.groovy.grails.commons.GrailsDomainClass
import org.junit.Before

class EnhanceDomainTestMixin {

  boolean enhancerMethodCalled = false;
  GrailsApplication application
  MetaMethod mockDomainMethod

  //replace the mockDomain Method from DomainClassUnitTestMixin with this closure
  def enhancedMockDomain = { Class cl, List list ->
    def enhanced =cl.metaClass.getMetaMethod("isEnhanced")
    try {
      //run the mockDomain method to have the mocked domain class registered in the grails application
      mockDomainMethod.invoke(delegate, cl, list)
    }
    finally {
      //enhance the grails domain with a new method
      def domain = application.getDomainClass(cl.name) as GrailsDomainClass
      domain.metaClass.isEnhanced = { return true; }
      assert domain.newInstance().isEnhanced();
    }
  }

  @Before void runDomainEnhancer() {
    enhancerMethodCalled = true;
    //GrailsUnitTestMixin.initGrailsApplication() should have already been called. (at least this was not an issue here)
    application = GrailsUnitTestMixin.grailsApplication

    //pick the mockDomain method
    mockDomainMethod = DomainClassUnitTestMixin.metaClass.pickMethod("mockDomain", Class, List)

    //if the picked mockDomain has never been enhanced, wrap it.
    if(mockDomainMethod != enhancedMockDomain) {
      DomainClassUnitTestMixin.metaClass.mockDomain = enhancedMockDomain
    }
  }
}

3) Add this small utils class (in test/unit/playground)

package playground

class TestSetup {

  static Data d1

  static void setup() {
    d1 = new Data()
    assert d1.isEnhanced()
  }
}

4) Add these tests into the unit test already created by grails DataTests

package playground

import grails.test.mixin.*

@TestFor(Data)
@TestMixin(EnhanceDomainTestMixin)
class DataTests {

  void testIsEnhancedLocal() {
    assert enhancerMethodCalled
    Data d = new Data()
    assert d.isEnhanced()
  }

  void testIsEnhancedLocalSecondTime() {
    assert enhancerMethodCalled
    Data d = new Data()
    assert d.isEnhanced()
  }

  void testIsEnhancedGlobalFirstTime() {
    assert enhancerMethodCalled
    TestSetup.setup()
    assert TestSetup.d1 != null
  }

  void testIsEnhancedGlobalSecondTime() {
    assert enhancerMethodCalled
    TestSetup.setup()
    assert TestSetup.d1 != null
  }

}

Now run this command: grails test-app unit:

you should have something like this output:

| Completed 4 unit tests, 4 failed in 1651ms
| Tests FAILED  - view reports in target\test-reports

Now run the this command again (sometime one more is needed):
grails test-app unit: playground.DataTests

testMixin> grails test-app unit: playground.DataTests
| Completed 4 unit tests, 0 failed in 1384ms
| Tests PASSED - view reports in target\test-reports

So does anyone has a clue of why the metaClass modification is not reliable while running unit tests ? And how to workaround this issue ?

4

1 回答 1

2

我必须在我的域类方法中使用 grailsApplication 配置。我遇到了同样的问题。尝试使用 Holders.config 而不是 grailsApplication.config。它对我有用。

于 2013-04-17T19:14:16.437 回答