Skip to content

WIP - Add testModifySite#1642

Merged
MaxGabriel merged 4 commits intomasterfrom
testModifySite
Nov 20, 2019
Merged

WIP - Add testModifySite#1642
MaxGabriel merged 4 commits intomasterfrom
testModifySite

Conversation

@MaxGabriel
Copy link
Copy Markdown
Member

@MaxGabriel MaxGabriel commented Nov 17, 2019

This PR is a draft to propose a new function testModifySite

In most Yesod apps, there is an App datatype at the core of the application, holding all configuration and state for the app (e.g. settings, connection pools, IORefs, etc.). In the tests, this is setup on a per-test basis. This PR adds a new function, testModifySite, to modify that site in the middle of a test. This is the signature:

testModifySite :: YesodDispatch site => (site -> site) -> Middleware -> YesodExample site ()

Here are some use cases I'm imagining:

Testing how modifying settings changes behavior

The yesod scaffold comes with an AppSettings type you use to configure your app. An app may want to have a test like so:

it "only fires missiles if config allows it" $ do
  -- Many
  -- common
  -- lines
  -- of
  -- database
  -- configuration
  post FireMissilesR
  -- Assert nothing happens
  testModifySite (\site -> site { appSettings = (appSettings site) { appSettingsFireMissiles = True } }) id
  post FireMissilesR
  -- Assert missiles are fired

Changing out stubbed functions

The use case we have at work is we have a Stubs datatype in our app, and we use it to stub out functions. This looks roughly like so:

data App = App
  { appTestStubs :: Stubs
  ...
  }

data Stubs = Stubs 
  { stubMailerHandlerSendEmail :: forall m. (MonadIO m) => Maybe (m ())
  ...
  }

withStub :: (Monad m, HasApp m) => (Stubs -> Maybe (m a)) -> m a -> m a
withStub f realImpl = do
  stubs <- appTestStubs <$> getApp
  fromMaybe realImpl (f stubs)

handlerSendEmail message = withStub stubMailerHandlerSendEmail $ do
  -- Real implementation here

And in production, we set stubMailerHandlerSendEmail to Nothing, in which case our codebase uses the production implementation, but in tests we can use the stubbed function to customize behavior.

testModifySite would allow us to modify that stub in the middle of a test, so we could have tests like this:

it "records sent emails in the database if sending succeeds" $ do
  -- Lots
  -- of 
  -- test 
  -- setup
  post SendEmailR
  -- Assert that SentEmail record exists in database
  testModifySite (\site -> site { appTestStubs = (appTestStubs site) { stubMailerHandlerSendEmail = Just . liftIO $ throwIO EmailSendException } }) id
  post SendEmailR
  -- Assert that new email record was not created

A gotcha about this function is that the yedSite and yedApp parameters are tied together. So, once the site is modified, a new yedApp must be created from it using toWaiAppPlain then calling a new middleware function. I think this is OK though?

One could maybe drop the Middleware parameter of testModifySite, but the Middleware initially provided would have to be stored in YesodExampleData for later, which would be a breaking change. Also, it adds complexity to YesodExampleData just for the convenience of this function.


If this function sounds good, I'll add another function called testSetSite that outright replaces the site value.

@MaxGabriel
Copy link
Copy Markdown
Member Author

After looking at usage a little more, the middleware parameter should probably be:

App -> IO Middleware

given that that's how the yesod scaffold generates it: https://github.com/yesodweb/yesod-scaffold/blob/3378e25e54472b7ea9f691b460ff46e9f9e8382c/test/TestImport.hs#L44

@snoyberg
Copy link
Copy Markdown
Member

Seems reasonable 👍

@MaxGabriel MaxGabriel marked this pull request as ready for review November 20, 2019 07:13
@MaxGabriel
Copy link
Copy Markdown
Member Author

Awesome, ready for review 👍

Copy link
Copy Markdown
Member

@snoyberg snoyberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This is good to merge and release once CI passes. Want to do the merge/release, or should I?

@MaxGabriel
Copy link
Copy Markdown
Member Author

I can take it

@MaxGabriel MaxGabriel merged commit d5f6fbb into master Nov 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants