| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Test.Polysemy.Mock
Synopsis
- class Mock (eff :: Effect) (m :: Type -> Type) where
- runMock :: (Mock eff m, Member (Embed m) r) => Sem (MockImpl eff m ': r) a -> Sem r (MockState eff m, a)
- evalMock :: (Mock eff m, Member (Embed m) r) => Sem (MockImpl eff m ': r) a -> Sem r a
- execMock :: (Mock eff m, Member (Embed m) r) => Sem (MockImpl eff m ': r) a -> Sem r (MockState eff m)
- class MockMany (effs :: EffectRow) m (r :: EffectRow) where
- type family MocksExist (xs :: EffectRow) m :: Constraint where ...
- type family MockChain (xs :: EffectRow) m (r :: EffectRow) :: Constraint where ...
- type family MockImpls (xs :: EffectRow) m where ...
- type family (xs :: [a]) :++: r :: [a] where ...
Documentation
class Mock (eff :: Effect) (m :: Type -> Type) where Source #
Here eff represents the effect being mocked and m is the side-effect
the mock implementation uses to keep MockState up to date.
Consder a Teletype effect defined as following that needs to be mocked:
data Teletype (m :: * -> *) a where Read :: Teletype m String Write :: String -> Teletype m () makeSem ''Teletype
A simple Mock instance for Teletype which always reads "Something"
when the Read action is called and records all the Write actions could
look like this:
instance Mock Teletype Identity where
data MockImpl Teletype Identity m a where
MockRead :: MockImpl Teletype Identity m String
MockWrite :: String -> MockImpl Teletype Identity m String
MockWriteCalls :: MockImpl Teletype Identity m [String]
data MockState Teletype Identity = MockState {writes :: [String]}
initialMockState = MockState []
mock = interpret $ case
Read -> send (MockImpl Teletype Identity) MockRead
Write s -> send (MockImpl Teletype Identity) $ MockWrite s
mockToState = reinterpretH $ case
MockRead -> pureT "Something"
MockWrite s -> do
(MockState w) <- get (MockState Teletype Identity)
put $ MockState (w ++ [s])
pureT ()
MockWriteCalls -> do
(MockState w) <- get (MockState Teletype Identity)
pureT w
Now with the help of this mock, a function which uses the Teletype effect
can be tested. Considering this is the function being tested:
program :: Member Teletype r => Sem r () program = do name <- read write $ "Hello " <> name
A test could look like this (using hspec):
spec :: Spec
spec = describe "program" $ do
it "writes hello message" $ do
let MockState recorededWrites =
runIdentity . runM . execMock $
mock Teletype Identity program
recorededWrites shouldBe ["Hello Something"]
Such a test can be written even without using this library. This class and the library are more useful when used with the template haskell generator for the mocks. The generator will produce a different mock than written above and it can be used like this:
genMock ''Teletype spec :: Spec spec = describe "program" $ do it "writes hello message" $ runMIO . evalMock do -- Setup what the Read action must return mockReadReturns $ pure "Something" -- Mock the Teletype effect while running the program mockTeletype @IO program -- Retrieve all the writes recordedWrites <- mockWriteCalls -- Make assertions about expected writes embed $ recordedWritesshouldBe["Hello Something"]
Associated Types
data MockImpl eff m :: Effect Source #
The effect which eff would be interpreted to when mock. This is also
used to provide more actions which allow inspecting arguments provided to
actions of eff and stubbing return values of actions in eff based on
the arguments.
The type to keep information about the mock. For example, it can be used to keep record of actions called on the effect and what to return on each call.
Methods
initialMockState :: MockState eff m Source #
Can be used to set default return values and initialize other attributes
of the MockState.
mock :: Member (MockImpl eff m) r => Sem (eff ': r) a -> Sem r a Source #
Interpret eff in terms of 'MockImpl eff'. The argument is usually a
value which is being tested.
mockToState :: Member (Embed m) r => Sem (MockImpl eff m ': r) a -> Sem (State (MockState eff m) ': r) a Source #
Interpret all actions of 'MockImpl eff m' to get 'State (MockImpl eff
m)'. The State effect could then be resolved using initialMockState.
Use runMock, evalMock or execMock for convinience.
runMock :: (Mock eff m, Member (Embed m) r) => Sem (MockImpl eff m ': r) a -> Sem r (MockState eff m, a) Source #
Run a mocked effect to get MockState and the effect value
execMock :: (Mock eff m, Member (Embed m) r) => Sem (MockImpl eff m ': r) a -> Sem r (MockState eff m) Source #
class MockMany (effs :: EffectRow) m (r :: EffectRow) where Source #
Mock many effects
Methods
mockMany :: MockChain effs m r => Sem (effs :++: r) a -> Sem r a Source #
Give a computation using a list of effects, transform it into a computation using Mocks of those effects
evalMocks :: (MocksExist effs m, Member (Embed m) r) => Sem (MockImpls effs m :++: r) a -> Sem r a Source #
Given a computation using Mock effects, evaluate the computation
type family MocksExist (xs :: EffectRow) m :: Constraint where ... Source #
Constraint to assert existence of mocks for each effect in xs for state effect m
Equations
| MocksExist '[] _ = () | |
| MocksExist (x ': xs) m = (Mock x m, MocksExist xs m) |