For our Hakyll codebase, I've written a few helper methods and have started adding some HSpec unit tests around newer ones e.g.:
-- | Reject an item unless @fieldName@ is set to "true"
unlessEnabled :: MonadMetadata m
=> String
-> Item a
-> m Bool
unlessEnabled fieldName item = do
maybeValue <- getMetadataBool (itemIdentifier item) fieldName
return $ maybe True not maybeValue
-- | Try to look up a boolean field ("true" maps to @Just True@)
getMetadataBool :: MonadMetadata m
=> Identifier
-> String
-> m (Maybe Bool)
getMetadataBool ident name = do
maybeString <- getMetadataField ident name
return $ ((== "true") . map toLower) <$> maybeString
Now creating an Item
or Identifier
for testing is easy enough, but I'm not sure where to go with the MonadMetadata
when running an Hspec.
I've seen testCompiler
which feels like it might be copyable / useful (Compiler
has a MonadMetadata
instance), but I'm out of my Haskell-depth here...
Eventually (and thanks to @Bergi's suggestions) I got something working, and then tidied up:
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
type KeyedValuesOf a = [(String, a)]
-- | Create Metadata from basic list of key-values -- stolen from Hakyll itself.
meta :: Yaml.ToJSON a => KeyedValuesOf a -> Metadata
meta pairs = HMS.fromList [(T.pack k, Yaml.toJSON v) | (k, v) <- pairs]
-- | A 'Reader' class of our own, with a specialised environment
-- for key-value metadata pairs.
newtype MonadMetadataReader a =
MonadMetadataReader {runMonadMetadataReader :: KeyedValuesOf String -> a}
deriving (Functor, Applicative, Monad)
-- | Simple 'Metadata' holder.
-- There are never any matches found,
-- and all metadata (for all items)
-- comes from from the Reader-like environment at setup.
instance MonadMetadata MonadMetadataReader where
getMetadata identifier = MonadMetadataReader meta
getMatches pattern = return []
Which allowed simple unit tests like:
it "enables for a value of \"false\"" $ do
let enabled = runMonadMetadataReader (unlessEnabled "key" item) [("key", "false")]
enabled `shouldBe` True
it "disables for a value of \"true\"" $ do
let enabled = runMonadMetadataReader (unlessEnabled "key" item) [("key", "true")]
enabled `shouldBe` False
it "enables if no metadata found" $ do
let enabled = runMonadMetadataReader (unlessEnabled "key" item) []
enabled `shouldBe` True