Mocking DefaultClient for unit tests? #1077
Description
Our web application uses Stormpath and in some unit tests we sometimes pull in the complete Stormpath client side stack in order to test some behavior or other.
But we don't want the Stormpath stack making remote calls. Achieving this isn't too complicated - we replace the usual DefaultClient
with one where we've mocked the InternalDataStore
so that it doesn't make remote connections.
I've put together a mini-project to demonstrate that this works well - mocked-data-store-client. I'll go into that in a minute.
This works - but our problem is that we're clearly mocking elements of the Stormpath stack that aren't really meant to be manipulated by the SDK's end users.
In particular the signature of the method DefaultClient.createDataStore
, which we override to plugin our mocked data store, has changed every few releases - it changed with 1.1.1 and I can see in Github that it will change again for the next version (the recent changes were by iancorcoran).
So the question is how should we be doing things when we do want to pull in the Stormpath stack for a unit test but don't want it making remote requests?
OK - let's go through my mini-project so you can see how we're doing things at the moment.
It's a super simple app with one REST controller DemoRestController
with one method that handles requests for the path /rest/email-address
. If the user is logged in it will return the email address stored in Stormpath for the current user, not logged in users will get a 401.
The app comes with a very basic MyappSecurityConfig
that calls StormpathWebSecurityConfigurer.stormpath()
.
Apart from additional utility code, to makes sure unauthorized REST calls result in a 401, rather than a 302 redirect to the Stormpath login page, that's it for src/main
part of things.
Then under test we have a test-application.properties
where stormpath.application.id
and stormpath.application.href
are set (to bogus values).
Then we have a very normal looking test DemoRestControllerTest
that uses MockMvc
etc. to call our /rest/email-address
logic. First as an unauthorized user to confirm that we get a 401 response. And then as an authorized user to confirm that we get back an email address.
If you just clone the repo and run mvn test
you should see this test run through fine.
So far nothing too surprising - but now the magic that this question is all about. The @SpringBootTest
annotation on our test pulls in our StormpathTestConfig
. This class is itself very simple:
- It just uses
@EnableAutoConfiguration
to pull in the kitchen sink, i.e. scan all our dependencies and pull in things like Stormpath. - It provides two
@Bean
annotated methods to replace standard Stormpath beans in order to achieve our goal of a Stormpath setup that makes no remote requests.
The second of these two beans is the interesting one - MockDataStoreClient
- this is a DefaultClient
subclass that creates and uses a mocked InternalDataStore
.
As remarked above it works fine, but it seems too internal to the SDK to be something end users of the SDK, like ourselves, should be messing with. It's the overridden createDataStore
method in this class that breaks often when we upgrade our Stormpath dependency version.
MockedSecurity
is the only remaining class - we use it in out simple DemoRestControllerTest
to mock up just enough stuff that we can get through StormpathAuthenticationProvider.authenticate
etc. and be seen as being authenticated when making a REST call from our test.