Safe Haskell | None |
---|---|
Language | Haskell2010 |
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 $ mockTeletype
Identity program recorededWritesshouldBe
["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 mock
Teletype @IO program -- Retrieve all the writes recordedWrites <- mockWriteCalls -- Make assertions about expected writes embed $ recordedWritesshouldBe
["Hello Something"]
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.
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
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
MocksExist '[] _ = () | |
MocksExist (x ': xs) m = (Mock x m, MocksExist xs m) |