packages feed

fixie-1.0.0: test/Test/Test/FixieSpec.hs

{-# OPTIONS_GHC -fno-warn-unused-top-binds -fno-warn-redundant-constraints #-}

{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}

module Test.Test.FixieSpec (spec) where

import Test.Hspec

import Control.Monad.Except (throwError, lift)
import Data.Void (Void)
import Test.Fixie


newtype Id a = Id Int
newtype DBError = DBError () deriving (Eq, Show)

data HTTPRequest = GET String
data HTTPResponse = HTTPResponse { responseStatus :: Int }
data HTTPError

class DBRecord a where
  procureRecord :: a

data User = User deriving (Eq, Show)
instance DBRecord User where
  procureRecord = User

class Monad m => DB m where
  fetchRecord :: DBRecord a => Id a -> m (Either DBError a)
  insertRecord :: DBRecord a => a -> m (Either DBError (Id a))

class Monad m => HTTP m where
  sendRequest :: HTTPRequest -> m (Either HTTPError HTTPResponse)

class Monad m => Throw m where
  throwMessage :: String -> m a

useDBAndHTTP :: (DB m, HTTP m, DBRecord r) => r -> m (Either DBError r)
useDBAndHTTP record = do
  (Right (Id recordId)) <- insertRecord record
  (Right response) <- sendRequest $ GET ("/record/" ++ show recordId)
  fetchRecord $ Id (responseStatus response)

mkFixture "Fixture" [ts| DB, HTTP, Throw |]

fixtureValueM :: Fixture (FixieM Fixture Void)
fixtureValueM = def

fixtureOutputM :: Fixture (FixieM Fixture e)
fixtureOutputM = def

fixtureValueT :: Monad m => Fixture (FixieT Fixture Void m)
fixtureValueT = def

fixtureOutputT :: Monad m => Fixture (FixieT Fixture e m)
fixtureOutputT = def

-- ensure generation of empty fixtures works
mkFixture "EmptyFixture" []

-- ensure fixtures can be generated for partially applied multi-parameter typeclasses
class MultiParam e m | m -> e where
  firstParam :: m e

mkFixture "MultiParamFixture" [ts| MultiParam Bool |]

spec :: Spec
spec = do
  describe "mkFixture" $ do
    it "generates a fixture type that can be used to stub out methods" $ do
      let fixture = def
            { _fetchRecord = \_ -> return $ Right procureRecord
            , _insertRecord = \_ -> return $ Right (Id 42)
            , _sendRequest = \_ -> return $ Right (HTTPResponse 200)
            }
      valueM fixture (useDBAndHTTP User) `shouldBe` Right User

    it "can handle partially applied multi parameter typeclasses" $ do
      let fixture = def { _firstParam = return True }
      valueM fixture firstParam `shouldBe` True

  describe "handle throws" $ do
    it "capture a thrown error message" $ do
      let message = "error message"
      let throw = throwMessage message >> return ()
      let fixture = def
            { _throwMessage = \msg -> do
                lift (msg `shouldBe` message)
                note "test test"
                note "abc"
                throwError msg
            }
      actual <- outputFunctionsNotesT fixture throw
      let expected = (Left "error message", ["throwMessage", "throwError"], ["test test", "abc"])
      actual `shouldBe` expected