Spring Integration Testing with Spock Mocks
We all know it’s important to write tests for our code. Often we write unit tests and mock out the dependencies for the class under test. But eventually there is something we need to test that we need a Spring Context spun up for with beans wired, security applied and properties injected. Even in those scenarios, however, there are times where we have a tricky or slow component that we would rather Mock than rely on a component. This post will cover how we can do that with Spock (or JUnit if that’s your thing).
Spring Integration Testing
Spring has lots of tools to help make integration testing easy and fast. Spring Boot makes things even easier. In Spring Boot 1.4 several improvements were introduced to reduce code, make your tests run faster and make edge cases easier to get around.
One of the nicest features introduced is the @MockBean annotation (as well as a @SpyBean). You can now quickly create Mockito mocks inside your test and have them injected into the Spring Context.
The problem is @MockBean is only supported using JUnit and Mockito. If you’re a fan of Spock, things are not quite as simple.
@TestConfiguration to the Rescue
As part of the Spring Boot 1.4 release, new @TestConfiguration and@TestComponent annotations were added to make it easy to define beans and configurations only intended for tests. This means we could create a mock and use it as a bean within the Spring context!
One caveat to note is how all of this impacts context caching. Mocks are part of the cache key, so it would be best if you can define a single SpringIntegrationTestsConfiguration annotated with @TestConfiguration so all of your integration tests as a whole run faster.
Injecting Spock Mocks into the Spring Context
If you want to use Spock mocks, the first thing you’ll need to do is make sure you’re on Spock version 1.1 or higher. This version introduced the ability to define mocks outside of a Spock Specification via the DetachedMockFactory. You’ll also need to include spock-spring if you don’t already have it. While you’re bringing new stuff in, check out spock-subjects-collaborators-extension for your unit tests.
Now to put it all together. Once you have everything in place, all we need to do is define our mock as a bean in the TestConfiguration.
Once we have our mock bean defined, which will take the place of our normal primary bean, we can utilize it in our specs by just injecting the bean and using it like normal.
We now have full control over our mock service, so if you wanted to test how things behave when throwing exceptions or returning different values based on inputs, you can do that just as quickly and easily as if you were writing a unit test.
EasyMock, Groovy Mocks, JMock, etc
The concepts above seem like they could easily be applied to other mocking techniques as long as you have the ability to define the mock outside of a test setup. If you can define the mock inside a @TestConfiguration class, yet describe the behavior in your tests, then you should be in business.
If you want to see how this works in more depth, the code is available on GitHub. The PersonControllerIntTest spins up a Spring context so we can make a MockMvc call to a REST endpoint which pulls data from an h2 database via a Spring Data repo, but the “Rank” data we would normally get from an external service has been mocked.
Test often and prosper!
One thought on “Spring Integration Testing with Spock Mocks”
Thanks very much for this. Your blog is the only correct solution that works properly with the current versions of Spring Boot and Spock. However, I ended up creating a base class for such tests and managed to move all the cruft out of the way into the base class. My actual test class is now really clean, concerned with just the test code.
You great – Works perfectly. Exactly what i was looking for. Thank you sir 🙂
Great stuff! Would it be possible to define a method call in the configuration as well? E.g. if the getRank method should return the same for all specifications rather than implementing the method in each specifications setup method/each features then block?
Or just use @SpringBean ExternalRankingService externalRankingServiceMock = Mock() – (for Spock) instead.
Thank you for that advice! Haven’t seen any drawbacks so far using this method.