{-| Helpers for creating database tests with hspec and pg-transact @hspec-pg-transact@ utilizes @tmp-postgres@ to automatically and connect to a temporary instance of @postgres@ on a random port. @ 'describeDB' migrate "Query” $ itDB "work" $ do 'execute_' [sql| INSERT INTO things VALUES (‘me’) |] 'query_' [sql| SELECT name FROM things |] `shouldReturn` [Only "me"] @ In the example above 'describeDB' wraps 'describe' with a 'beforeAll' hook for creating a db and a 'afterAll' hook for stopping a db. . Tests can be written with 'itDB' which is wrapper around 'it' that uses the passed in 'TestDB' to run a db transaction automatically for the test. The libary also provides a few other functions for more fine grained control over running transactions in tests. -} {-# LANGUAGE RecordWildCards #-} module Test.Hspec.DB where import Control.Exception import Control.Monad import qualified Data.ByteString.Char8 as BSC import Data.Pool import qualified Database.Postgres.Temp as Temp import Database.PostgreSQL.Simple import Database.PostgreSQL.Transact import Test.Hspec data TestDB = TestDB { tempDB :: Temp.DB -- ^ Handle for temporary @postgres@ process , pool :: Pool Connection -- ^ Pool of 50 connections to the temporary @postgres@ } -- | Start a temporary @postgres@ process and create a pool of connections to it setupDB :: (Connection -> IO ()) -> IO TestDB setupDB migrate = do tempDB <- either throwIO return =<< Temp.startAndLogToTmp [] putStrLn $ Temp.connectionString tempDB pool <- createPool (connectPostgreSQL (BSC.pack $ Temp.connectionString tempDB)) close 1 100000000 50 withResource pool migrate return TestDB {..} -- | Drop all the connections and shutdown the @postgres@ process teardownDB :: TestDB -> IO () teardownDB TestDB {..} = do destroyAllResources pool void $ Temp.stop tempDB -- | Run an 'IO' action with a connection from the pool withPool :: TestDB -> (Connection -> IO a) -> IO a withPool testDB = withResource (pool testDB) -- | Run an 'DB' transaction. Uses 'runDBTSerializable'. withDB :: DB a -> TestDB -> IO a withDB action testDB = withResource (pool testDB) (runDBTSerializable action) -- | Flipped version of 'withDB' runDB :: TestDB -> DB a -> IO a runDB = flip withDB -- | Helper for writing tests. Wrapper around 'it' that uses the passed -- in 'TestDB' to run a db transaction automatically for the test. itDB :: String -> DB a -> SpecWith TestDB itDB msg action = it msg $ void . withDB action -- | Wraps 'describe' with a -- -- @ -- 'beforeAll' ('setupDB' migrate) -- @ -- -- hook for creating a db and a -- -- @ -- 'afterAll' 'teardownDB' -- @ -- -- hook for stopping a db. describeDB :: (Connection -> IO ()) -> String -> SpecWith TestDB -> Spec describeDB migrate str = beforeAll (setupDB migrate) . afterAll teardownDB . describe str