Spring Boot 1.4 @WebMvcTest support #624

Open
AlexejK opened this Issue Jul 26, 2016 · 31 comments

Projects

None yet

7 participants

@AlexejK
AlexejK commented Jul 26, 2016

Support for @SpringTest has been added as part of #581 , however this only provides basic functionality and does not allow to utilise the power of Spring Boot 1.4 and it's testing enhancements in a complete way.

Basic Web MVC Test for a controller without any dependencies that need to be @Autowired will go through. Spring framework 4.3 provides constructor injection enhancements and even prior to 4.3 it's recommended to use constructor injection for component dependencies. @WebMvcTest when used with JUnit/Mockito empowers developer to automatically inject mock beans with help of new @MockBean annotation.

This naturally does not work with Spock and mocks created via Spocks Mock() are not injected into the constructor as dependencies, making it much harder to work with Spring Boot and Web MVC Tests.

@kamaydeo
kamaydeo commented Aug 5, 2016

+1 for @MockBean support.

@lavcraft
lavcraft commented Aug 7, 2016

And @SpyBean support was needed too

@leonard84
Contributor

Duplicates @MockBean and @SpyBean Issue #629

@AlexejK
AlexejK commented Aug 18, 2016

Technically it's the other way around (this one has been open for 2weeks longer) but yes - They are about the same problem.

@leonard84
Contributor

@AlexejK yes, but the title of the other one reflects the issue better and it has an example ;)

@AlexejK
AlexejK commented Aug 18, 2016

One thing I believe that issue does not reflect is the bean injection especially within webmvc tests where as of new spring version, constructor injection happens automatically.

The #629 issue attempts to use mockito specific annotation which shouldn't logically work, but this issue in fact that there is no way to provide spring with information about spock mocks so it can do injection.

With this said, I no longer believe it's a duplicate, even though they are touching similar subjects.

@lavcraft

@AlexejK yes, I used spring boot mockito annotation. But i will use similar Spock annotations for spring boot test if it exists

@leonard84
Contributor
leonard84 commented Aug 18, 2016 edited

@AlexejK as I already said in #629 the upcoming version of spock lets you create mocks outside the context of a specification, e.g. in an application context, so they can get injected.

You can also use an embedded Configuration https://github.com/spockframework/spock/blob/master/spock-spring/src/test/groovy/org/spockframework/spring/MockInjectionWithEmbeddedConfig.groovy

@snekse
snekse commented Aug 18, 2016

@leonard84 I tried the suggested config detached mock, but on a @WebMvcTest, it prevents MockMvc from being injected into the spec.

NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.test.web.servlet.MockMvc]

If I remove loader=AnnotationConfigContextLoader, then the MockMvc gets wired correctly.

Granted, my tests still aren't working correctly because it seems like it doesn't want to pick up my interaction definitions, but I'm still investigating that.

@leonard84
Contributor

@snekse could you provide a SSCCE

@kiview
kiview commented Aug 19, 2016

What do you think about introducing a new Spock-specific annotation (@SpockWebMvcTest), which will duplicate the behaviour of @WebMvcTest but with Spock Mocks? This would be kind of hackish, but we'd have a solution.

@leonard84
Contributor

@snekse this missing {lang} was the main issue and it seems that AnnotationConfigContextLoader does not work well with @WebMvcTest, however it works fine if you just remove it.

Index: src/test/groovy/org/spockframework/controllers/GreetingControllerDetachedFactoryTest.groovy
===================================================================
--- src/test/groovy/org/spockframework/controllers/GreetingControllerDetachedFactoryTest.groovy (revision 7325006cb2386bd56cdf600e38fbc1b3a81056ef)
+++ src/test/groovy/org/spockframework/controllers/GreetingControllerDetachedFactoryTest.groovy (revision )
@@ -29,7 +29,7 @@
  *
  * @see GreetingNoDependencyControllerTest
  */
-@ContextConfiguration(loader=AnnotationConfigContextLoader)
+@ContextConfiguration
 @WebMvcTest(GreetingController)
 class GreetingControllerDetachedFactoryTest extends Specification {

Index: src/main/groovy/org/spockframework/controllers/GreetingController.groovy
===================================================================
--- src/main/groovy/org/spockframework/controllers/GreetingController.groovy    (revision 7325006cb2386bd56cdf600e38fbc1b3a81056ef)
+++ src/main/groovy/org/spockframework/controllers/GreetingController.groovy    (revision )
@@ -14,7 +14,7 @@
     @Autowired GreetingService greetingService

     @SuppressWarnings("GrMethodMayBeStatic")
-    @GetMapping(path = "/")
+    @GetMapping(path = "/{lang}")
     String greetWorld(@PathVariable String lang) {
         return greetingService.greet(lang)
     }
@snekse
snekse commented Aug 19, 2016

Doh. That's what I get making changes late stages. The injection is indeed working properly. I'll update my project to reflect the proper way.

@kiview
kiview commented Aug 19, 2016 edited

It's kind of a bug, that we still have to provide the @ContextConfiguration in order for Spock to identify the test as an integration test. I thought I've caught all cases in this #610, but the support for this annotation is obviously missing on multiple levels.

Since it seems we need some special code for handling @WebMvcTest, I might be able to implement some code which will automatically create the needed Beans using a DetachedMockFactory, what do you think about this?


This seems a bit strange to me, we already have a test for @WebMvcTest, which at least ensures, that the spring context is initialized:
https://github.com/spockframework/spock/blob/2b5892446f2310b806062666118a5ccdf2855609/spock-spring/boot-test/src/test/groovy/org/spockframework/boot/WebMvcTestIntegrationSpec.groovy

Maybe this PR is not part of RC-1.

@leonard84
Contributor

Yes #610 is not part of rc-1, it is part of rc-2 which should have been published, but there are issues with sonatype.

As for the other part, it depends on the complexity. Looking at https://github.com/spring-projects/spring-boot/tree/master/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito there is a whole lot of stuff going on. I'm not convinced that the complexity is worth it, and keep in mind that it needs to be backwards compatible to the existing code base.

Although it is a bit more ceremony compared to @MockBean it is still rather simple and if you put the factory in a base class then it's just the @Bean declaration.

  static class Config {
        private DetachedMockFactory factory = new DetachedMockFactory()

        @Bean
        GreetingService greetingService() {
            factory.Mock(GreetingService, name:"greetingService")
        }
    }
@kiview
kiview commented Aug 19, 2016

I agree, The spock-spring module is already kind of cluttered in order to stay mostly backwards compatible, looking for lot's of different annotations and stuff.

I think this is the class which functionality we'd need to replicate:
MockitoPostProcessor

We should also think about the rarity of defining Stubs/Mocks without specified behavior (which is what the spring implementation mainly does). So if we'd try to come up with a truly useful solution we'd need some way to specify the intended behavior as well.

However I'd like to document your proposed approach of using a DetachedMockFactory and @Bean definition somewhere. Maybe we can even update the spring-boot documentation itself to include this workaround (there is already a tiny paragraph about Spock in the official documentation).

@leonard84
Contributor

@kiview more documentation is always good. You could also extend the boot-test project to include an example Spec.

Spock uses AST rewriting to have the powerful mock behavior dsl, the problem with that is that it requires the closure source to be in the context of the Specification. You can't just apply any closure to the mock object. And Spock mocks don't work outside of a Specification, with the DetachedMockFactory they can be created outside but they can't have any behavior until they are attached to a Specification.

@snekse
snekse commented Aug 20, 2016

If I could write a spec how I normally write specs, but annotate the class like @SpockSpringBeans(classes=[FooService,BarService]), that would be pretty cool.

Imagining that annotation would basically just be defining a config to be used and the classes it would mock with the detached factory. Then all of the interaction closures would be defined in the spec just like all of my other specs.

@kiview
kiview commented Aug 20, 2016

@snekse I think that's how Grails does it as well (at least they did it like this in version 2.x), I'm not sure what happens if you use Spock with Grails, maybe they already have some code for this functionality? It might be a good idea to look into Grails' mocking facilities.

@leonard84
Contributor

It looks like they use their own mocking implementation for @Mock(..). The only example with Spock mocks was this.

@FreshRuntime
@TestMixin(GrailsUnitTestMixin)
class MockedBeanSpec extends Specification {
    def myService=Mock(MyService)

    def doWithSpring = {
        myService(InstanceFactoryBean, myService, MyService)
    }

    def "doWithSpring callback is executed"() {
        when:
        def myServiceBean=grailsApplication.mainContext.getBean('myService')
        myServiceBean.prova()
        then:
        1 * myService.prova() >> { true }
    }
}

Here they manually construct the service bean in doWithSpring.

@snekse
snekse commented Aug 23, 2016

Just throwing in a bit of an update. My example doesn't include security and for some reason, some of the things we were doing would whack MVC mappings when using the @Configuration static class Config. We would get 404 errors despite knowing the mapping existed.

I was able to resolve this by pulling the config outside of the spec and @Import(MySpecConfig)

We're still using the DetachedMockFactory, so everything inside the spec looks like a basic unit test.

@leonard84
Contributor

Any idea why @Configuration breaks things, kinda sounds like a spring boot bug?

@knizamov
knizamov commented Aug 27, 2016 edited

@snekse @leonard84 http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-detecting-config

If you want to customize the primary configuration, you can use a nested @TestConfiguration class. Unlike a nested @Configuration class which would be used instead of a your application’s primary configuration, a nested @TestConfiguration class will be used in addition to your application’s primary configuration.

Try changing nested @Configuration to @TestConfiguration in the snekse's example.

@kiview
kiview commented Aug 29, 2016 edited

@leonard84 I've talked with someone from the Pivotal Spring-Boot team at the weekend and he suggested as well, that we do an implementation which is similar to the MockMvcBean annotation and provide a custom Spock annotation.

There a currently no plans to support others mocking frameworks than Mockito.

@leonard84
Contributor

@kiview this could be implemented as an extra module/extension, since it does not require changes to Spock itself to work. And this way it would be easier to only support newer spring versions.

@kiview
kiview commented Aug 29, 2016

@leonard84 I think we could do it like this, dropping backwards compatibility seems like a huge boon. So do you think we should add a new module to Spock, or should I create a new repository for this?

@kiview
kiview commented Aug 29, 2016

@knizamov If uploaded a working example using DetachedMockFactory and @TestConfiguration on my Github Profile:
https://github.com/kiview/spring-spock-mock-beans-demo/tree/master

@leonard84
Contributor

@kiview I would put it in a separate repo for now, similar to https://github.com/marcingrzejszczak/spock-subjects-collaborators-extension. This way it can follow the faster spring boot release cycles.

@leonard84
Contributor

@kiview I've added a little more documentation to the spring module in PR #641 with an example for @WebMvcTest as well. Any comments?

@kiview
kiview commented Aug 30, 2016

@leonard84 The documentation looks really useful. Maybe we can even link to the docs from inside the Spring-Boot docs (there is a tiny Spock chapter after all), once the PR has been merged.

Regarding the extension:
Alright, then I'll start on developing a new Spock extension in a new repository in the following days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment