Skip to content

Instantly share code, notes, and snippets.

@aerohit
Last active July 9, 2020 05:36
Show Gist options
  • Save aerohit/5a4e774cb872a015a8cf0e0c40fd0973 to your computer and use it in GitHub Desktop.
Save aerohit/5a4e774cb872a015a8cf0e0c40fd0973 to your computer and use it in GitHub Desktop.
Unit testing Tagless Final
-- Reference: https://jproyo.github.io/posts/2019-03-17-tagless-final-haskell.html
newtype UserName = UserName String deriving Show
newtype DataResult = DataResult String deriving (Show, Eq)
newtype AppConfig = AppConfig String deriving Show
newtype LogMsg = LogMsg String deriving Show
class Monad m => Cache m where
getFromCache :: UserName -> m (Maybe DataResult)
storeCache :: UserName -> DataResult -> m ()
class Monad m => DataSource m where
getFromSource :: UserName -> m DataResult
class Monad m => Logger m where
logMsg :: LogMsg -> m ()
class Monad m => ConfigProvider m where
getConfig :: m AppConfig
requestData
:: (ConfigProvider m, Cache m, DataSource m, Logger m)
=> UserName
-> m DataResult
requestData userName = do
cfg <- getConfig
logMsg $ LogMsg $ show cfg <> " is read as config"
cache <- getFromCache userName
case cache of
Nothing -> do
logMsg $ LogMsg $ show userName <> " not found in cache"
result <- getFromSource userName
logMsg $ LogMsg $ show userName <> " found in datasource"
storeCache userName result
return result
Just result -> do
logMsg $ LogMsg $ show userName <> " found in cache"
return result
---- IO instances for PRODUCTION
instance Cache IO where
getFromCache _ = return Nothing
storeCache _ _ = return ()
instance DataSource IO where
getFromSource userName = return $ DataResult $ "Data: " <> show userName
instance Logger IO where
logMsg msg = print $ show msg
instance ConfigProvider IO where
getConfig = return $ AppConfig "HelloApp"
runApp :: IO ()
runApp = do
r <- requestData $ UserName "rohit"
print r
---- TESTING scenarios
-- 1 - data found in cache
-- 2 - data not found in cache, but found in data source
-- 3 - data not found in cache and not found in data source
-- Scenario 1: found in cache
newtype InCache a =
InCache { unInCache :: Identity a }
deriving (Show, Functor, Applicative, Monad)
instance Cache InCache where
getFromCache _ = InCache $ return $ Just $ DataResult "Data 1"
storeCache _ _ = undefined
instance DataSource InCache where
getFromSource _ = undefined
instance ConfigProvider InCache where
getConfig = return $ AppConfig "config"
instance Logger InCache where
logMsg _ = return ()
testDataInCache :: InCache Bool
testDataInCache = do
r <- requestData $ UserName "name 1"
return $ r == DataResult "Data 1"
-- Scenario 2: not found in cache, but found in data source
newtype NotInCache a =
NotInCache { unNotInCache :: Identity a }
deriving (Show, Functor, Applicative, Monad)
instance Cache NotInCache where
getFromCache _ = NotInCache $ return Nothing
storeCache _ _ = return ()
instance DataSource NotInCache where
getFromSource _ = return $ DataResult "Data 2"
instance ConfigProvider NotInCache where
getConfig = return $ AppConfig "config"
instance Logger NotInCache where
logMsg _ = return ()
testDataAbsentInCacheButPresentInDataSource :: NotInCache Bool
testDataAbsentInCacheButPresentInDataSource = do
r <- requestData $ UserName "name 1"
return $ r == DataResult "Data 2"
---- TODO: Scenario 3: skipped NeitherFoundInCacheNorDataSource meaning one more type class and four more instances to test this scenario
testAll = do
test1 <- unInCache testDataInCache
test2 <- unNotInCache testDataAbsentInCacheButPresentInDataSource
return $ test1 && test2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment