Closed gregopet closed 11 years ago
I will need to implement some better support for this. Right now you need to do the defineBeans
step in setupSpec
– not setup
. I'd like unit test support to be seamless so you don't need to register anything and as GSON
just works.
That would be great, thanks for all your work!
Quick question (I don't expect an answer unless it's obvious to you at first glance): I wanted to simplify my tests by having my tests extend a class in which the GSON bean is defined, so I wrote something like this:
@TestMixin(GrailsUnitTestMixin)
class ApiControllerSpec extends Specification {
void setupSpec() {
//make GSON work in tests
defineBeans {
proxyHandler DefaultEntityProxyHandler
domainSerializer GrailsDomainSerializer, ref('grailsApplication'), ref('proxyHandler')
domainDeserializer GrailsDomainDeserializer, ref('grailsApplication')
gsonBuilder(GsonBuilderFactory) {
pluginManager = ref('pluginManager')
}
}
}
void setup() {
controller.gsonBuilder = applicationContext.getBean('gsonBuilder', GsonBuilder)
}
}
But when I have my actual specification extend it, I get the following exception: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.codehaus.groovy.grails.support.proxy.ProxyHandler] is defined: expected single bean but found 2: grailsProxyHandler,proxyHandler
Any clues? :)
Looks like (although I'd need to confirm this by digging into the code) that the Grails unit test framework is injecting a bean called grailsProxyHandler into the application context. Something else (and what that is should be obvious from the stack trace) is then retrieving beans with the type ProxyHandler
and asserting that there is only one.
This is at odds with "reality" in a running Grails app where the default ProxyHandler
bean injected by Grails is called proxyHandler so the Gson plugin simply overrides it. Unfortunately this kind of thing is pretty common, it often seems that things behave in subtly different ways between unit test land and reality. I'm really not a fan of some of what goes on in the Grails unit test support as bootstrapping a complex Spring application context is not exactly promoting good unit isolation.
I'm going to add some unit tests to the test app (currently it just has functional tests) so I can try to improve the unit test support provided by the plugin.
The reason for the NoSuchBeanDefinitionException
is that in unit tests the property applicationContext
is not the same instance as grailsApplication.mainContext
. Beans created with defineBeans
are present in the former but not the latter. That seems to me like a bug with Grails; it just makes no sense at all.
Looks like I've found a solution. Looking into the converters plugin I see that it automatically injects the application context into the converter if it implements ApplicationContextAware
. See https://github.com/grails/grails-core/blob/master/grails-plugin-converters/src/main/groovy/org/codehaus/groovy/grails/web/converters/ConverterUtil.java#L85
This means I can just implement that interface in GSON
and stop using the static ApplicationHolder
.
I have a working unit test example in 4f32874. I'm going to work on extracting the common parts out as a test mixin.
I have released a 1.1.2-SNAPSHOT containing the test mixin. It would be great if you could give it a try and see if it helps. You should just need to add @TestMixin(GsonUnitTestMixin)
to your test/spec class & you can then remove all the defineBeans
stuff.
As well as wiring up the GSON converter properly the mixin adds render(GSON)
to controllers, <init>(JsonObject)
and setProperties(JsonObject)
to domains and getGSON()
to HttpServletRequest. All of those are the same as in a running app. You can also use response.GSON
to get a JsonElement
from the response in controller unit tests.
If everything is behaving correctly for you I'll release 1.1.2 ASAP.
Unfortunately, sometimes it works and sometimes it doesn't. If I run the unit tests by invoking grails run-app
then I'd say I get an exception in about 1/3 of all runs. There seem to be two distinct stacktaces appearing at random:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.codehaus.groovy.grails.support.proxy.ProxyHandler] is defined: expected single bean but found 2: grailsProxyHandler,proxyHandler
at grails.test.mixin.web.ControllerUnitTestMixin.configureGrailsWeb(ControllerUnitTestMixin.groovy:216)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
at org.spockframework.runtime.extension.builtin.JUnitFixtureMethodsExtension$FixtureType$FixtureMethodInterceptor.intercept(JUnitFixtureMethodsExtension.java:145)
at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:84)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
and
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'gsonBuilder' is defined
at grails.plugin.gson.test.GsonUnitTestMixin.enhanceApplication(GsonUnitTestMixin.groovy:31)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
at org.spockframework.runtime.extension.builtin.JUnitFixtureMethodsExtension$FixtureType$FixtureMethodInterceptor.intercept(JUnitFixtureMethodsExtension.java:145)
at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:84)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
Both of them prevent any tests from that specification from being run. If I start a grails interactive shell and then issue test-app
, however, then the tests seem to always run successfully - though perhaps I've just been always winning some Grails bootstrap lottery every time I started up the interactive mode...
@gregopet not sure what's going on there. I'm not seeing the problem with the duplicate ProxyHandler beans in the unit test I've added.
The second one is very odd as the exception is getting thrown from an @Before method and complaining that a bean defined in an @BeforeClass method is not there. I wonder if the order the mixins are executed is non-deterministic and my definitions are sometimes getting overridden by those in the standard Grails mixins.
So much black magic, it's very hard to debug :(
Damn, I just got the proxyHandler thing too.
Grails does seem like black magic sometimes, especially if one doesn't have a strong Java & Spring background.
If I can help you narrow these down in any way, please let me know - these bugs that only happen sometimes are damned annoying.
I've updated the snapshot. Can you see if that fixes the problem. I was able to recreate both the problems you were having and I think I've now resolved them.
It works!
I re-ran the tests over 20 times just now without a single problem. And the init/properties/response.GSON/render worked for me as well.
Thank you!
Great. I'll get the release done so you can stop using a snapshot.
Thanks for your help tracking everything down.
So how do you make this tests now work? Do I have to use the codesnippet from above? (I modifed it to run with JUnit with @BeforeClass and @Before) I'm using grails 2.2.2
I'm getting this stacktrace:
| Failure: <my.test.class>
| groovy.lang.MissingPropertyException: No such property: DefaultEntityProxyHandler for class: <my.test.class>
at <my.test.class>$_setupSpec_closure1.doCall(TicketControllerTests.groovy:24)
at grails.spring.BeanBuilder.invokeBeanDefiningClosure(BeanBuilder.java:757)
at grails.spring.BeanBuilder.beans(BeanBuilder.java:584)
at grails.test.mixin.support.GrailsUnitTestMixin.defineBeans(GrailsUnitTestMixin.groovy:74)
at <my.test.class>.setupSpec(TicketControllerTests.groovy:23)
| Failure: <my.test.class>
| java.lang.NullPointerException: Cannot invoke method isActive() on null object
at grails.test.mixin.support.GrailsUnitTestMixin.shutdownApplicationContext(GrailsUnitTestMixin.groovy:234)
my code:
@BeforeClass
static void setupSpec() {
//make GSON work in tests
defineBeans {
proxyHandler DefaultEntityProxyHandler
domainSerializer GrailsDomainSerializer, ref('grailsApplication'), ref('proxyHandler')
domainDeserializer GrailsDomainDeserializer, ref('grailsApplication')
gsonBuilder(GsonBuilderFactory) {
pluginManager = ref('pluginManager')
}
}
}
@Before
void setup() {
controller.gsonBuilder = applicationContext.getBean('gsonBuilder', GsonBuilder)
}
@mlem looks like you're missing an import for DefaultEntityProxyHandler
I'm trying to write a unit test for a simple controller that takes GSON as input and writes to DB. GSON because, the input has nested objects.
Controller implementation: def create() {
def artist = new Artist(request.GSON)
if (!artist.save(flush: true)) {
artist.errors.each {
log.error("Error while creating Artist " + it)
}
render status: 500, layout: null
return
}
response.status = 201
render artist as GSON
}
Unit test:
@TestMixin(GsonUnitTestMixin) @TestFor(ArtistController) @Mock(Artist) class ArtistControllerTests {
void testCreate() {
request.GSON = "{guid:123,"+
"name: 'Akon',"+
"albums: ["+
"{guid:1,"+
"name:'album 1'"+
"}]}"
controller.create()
assert response.status == 201
}
}
Exception: Cannot get property 'manyToOne' on null object at def artist = new Artist(request.GSON) in the controller
Can someone please help me?
When using the
render xyz as GSON
syntax, I cannot seem to be able to unit test these controllers. Running the unit tests (using Spock) I get the following exception:org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'gsonBuilder' is defined
I have defined the
gsonBuilder
bean using thedefineBeans
directive and in fact I can unit test the controller if I use the alternative syntax (injecting thegsonBuilder
bean, creating agson
object and then calling.toJson(xyz)
on it. As the as GSON syntax is shorter and more expressive, I would prefer it over the currently testable one.In case there is a way to make unit tests work and I just don't know how, maybe the documentation could mention it?