polysemy-mocks-0.1.0.0: test/Test/Polysemy/Mock/TeletypeSpec.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Test.Polysemy.Mock.TeletypeSpec where
import Data.Functor.Identity
import Data.Kind
import Polysemy
import Polysemy.Internal (send)
import Polysemy.State
import Test.Hspec
import Test.Polysemy.Mock
import Prelude hiding (read)
data Teletype (m :: Type -> Type) a where
Read :: Teletype m String
Write :: String -> Teletype m ()
read :: Member Teletype r => Sem r String
read = send Read
write :: Member Teletype r => String -> Sem r ()
write = send . Write
instance forall n. Mock Teletype n where
data MockImpl Teletype n m a where
MockRead :: MockImpl Teletype n m String
MockWrite :: String -> MockImpl Teletype n m ()
MockReadReturns :: n String -> MockImpl Teletype n m ()
MockWriteReturns :: (String -> n ()) -> MockImpl Teletype n m ()
MockReadCalls :: MockImpl Teletype n m [()]
MockWriteCalls :: MockImpl Teletype n m [String]
data MockState Teletype n = MockState
{ readCalls :: [()],
writeCalls :: [String],
readReturns :: n String,
writeReturns :: String -> n ()
}
initialMockState = MockState [] [] (error "Unimplemented") (error "Unimplemented")
mock = interpret $ \case
Read -> send @(MockImpl Teletype n) MockRead
Write s -> send @(MockImpl Teletype n) $ MockWrite s
mockToState = reinterpretH $ \case
MockRead -> do
state <- get @(MockState Teletype n)
put state {readCalls = readCalls state ++ [()]}
pureT =<< embed (readReturns state)
MockWrite s -> do
state <- get @(MockState Teletype n)
put state {writeCalls = writeCalls state ++ [s]}
pureT =<< embed (writeReturns state s)
MockReadReturns f -> do
state <- get @(MockState Teletype n)
put state {readReturns = f}
pureT ()
MockWriteReturns f -> do
state <- get @(MockState Teletype n)
put state {writeReturns = f}
pureT ()
MockReadCalls -> do
state <- get @(MockState Teletype n)
pureT (readCalls state)
MockWriteCalls -> do
state <- get @(MockState Teletype n)
pureT (writeCalls state)
mockWriteReturns :: (String -> m ()) -> Sem '[MockImpl Teletype m, Embed m] ()
mockWriteReturns = send . MockWriteReturns
mockReadReturns :: m String -> Sem '[MockImpl Teletype m, Embed m] ()
mockReadReturns = send . MockReadReturns
mockReadCalls :: forall m. Sem '[MockImpl Teletype m, Embed m] [()]
mockReadCalls = send @(MockImpl Teletype m) MockReadCalls
mockWriteCalls :: forall m. Sem '[MockImpl Teletype m, Embed m] [String]
mockWriteCalls = send @(MockImpl Teletype m) MockWriteCalls
program :: Member Teletype r => Sem r ()
program = do
write "Name: "
name <- read
write $ "Hello " <> name
{-# ANN spec ("HLint: ignore Redundant do" :: String) #-}
spec :: Spec
spec =
describe "program" $ do
it "greets" $ runM @IO . evalMock $ do
mockWriteReturns (const $ pure ())
mockReadReturns (pure "Akshay")
mock @Teletype @IO program
writes <- mockWriteCalls
embed $ writes `shouldBe` ["Name: ", "Hello Akshay"]
it "greets without IO" $ do
let state = runIdentity . runM @Identity . execMock $ do
mockWriteReturns (const $ pure ())
mockReadReturns (pure "Akshay")
mock @Teletype @Identity program
writeCalls state `shouldBe` ["Name: ", "Hello Akshay"]