stormpath / stormpath-sdk-java

Official Java SDK for the Stormpath User Management REST API
222 stars 155 forks source link

Mocking DefaultClient for unit tests? #1077

Open george-hawkins-work opened 8 years ago

george-hawkins-work commented 8 years ago

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:

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.

mvrogowski commented 7 years ago

It's a very clever solution! I would like to have it in Pyhton SDK as well. :)