# Mockazo 👃 _Mock your records of functions with ease_ [![CircleCI](https://circleci.com/gh/theam/mockazo.svg?style=svg&circle-token=e80d08cb0d9855a774709311335f4e29ca40f5de)](https://circleci.com/gh/theam/mockazo) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/theam/mockazo.svg)](http://isitmaintained.com/project/theam/mockazo "Average time to resolve an issue") [![GitHub license](https://img.shields.io/github/license/theam/mockazo.svg)](https://github.com/theam/mockazo/blob/master/LICENSE) [![Hackage version](https://img.shields.io/hackage/v/mockazo.svg)](https://hackage.haskell.org/package/mockazo) [![Open Source Love png1](https://badges.frapsoft.com/os/v1/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/) One approach to structure a Haskell is using records of functions, sometimes called handles, services, or as we like to call them, components. Mockazo provides a way of mocking components with ease and to verify that they executed the proper operations with the proper results. # Adding it to your project Add the `mockazo` dependency to your `package.yaml` **or** your `cabal` file. If you use Stack, you also need to add `multistate-0.8.0.2` to your `extra-deps` section in the `stack.yaml` file. In your tests, `import Data.Component.Mock`, and you are ready to roll! # Some restrictions For Mockazo to work properly, we need that you do a little tweaking on your component definitions: ## Parametrize the return context It is common practice to make component methods return values in the `IO` context. This might looks straightforward, but when mocking comes into place, it is much easier to work in other contexts. Imagine that we have a simple logging component: ```haskell data Component = Component { logInfo :: Text -> IO () , logWarn :: Text -> IO () , logError :: Text -> IO () } ``` For Mockazo to work properly, we parametrize the context of execution: ```haskell data Component context = Component { logInfo :: Text -> context () , logWarn :: Text -> context () , logError :: Text -> context () } ``` This not only makes testing easier, but also makes your code much more robust, because when defining a function that uses this component, we are unable to execute any other kind of code that runs in another execution contexts (like a colleague calling `launchMissiles :: IO ()`). ## All methods must return something in a context Generally, we use Components to model pieces of our application that perform side effects, so adding a field that contains some static piece of data doesn't make much sense. If you really need to do this, we recommend you that you create a companion `Configuration` type, with all of these values, and leave the component for the side effect operations only. If you **really** need the value inside of the component, wrap it in `context`. Mockazo, expects all the methods to be effectful. So it will choke on a return value that is not wrapped in the context. So, instead of doing: ```haskell data Component context = Component { foo :: Text } ``` Do this: ```haskell data Component context = Component { foo :: context Text } ``` # Creating your first mock Let's suppose that we want to mock the logging component from the first example: ```haskell data Component context = Component { logInfo :: Text -> context () , logWarn :: Text -> context () , logError :: Text -> context () } ``` Create a separate module for the mock (we recommend you to do it in `test/Mock`, and the name of the module should match the name of the component module). After that, we create the mock (following the advice, we make it in `test/Mock/Logging.hs`): ```haskell module Mock.Logging where import Logging -- We import the component *UNQUALIFIED* makeMock ''Component ``` That's it! (Yes, really) If for some reason, you want to add the export list to the mock module (your compiler is complaining), you can fix it like this: ```haskell module Mock.Logging (Action(..), Component(..), mock) where import Logging makeMock ''Component ``` # Testing a function that calls our component Suppose that somewhere we have a function `importantOperation` that looks like this: ```haskell importantOperation :: Monad context => Logging.Component context -> context () importantOperation Logging.Component{..} = do logInfo "info" logWarn "warn" logError "error" ``` We want to assure that these operations are run in order and with the appropriate arguments. We can write a test for it by using Mockazo's little DSL. In our tests file: ```haskell import qualified Mock.Logging as Logging let loggingMock = Logging.mock -- ... somewhere in our test framework runMock $ withActions [ Logging.LogInfo "info" :-> () , Logging.LogWarn "warn" :-> () , Logging.LogError "error" :-> () ] $ importantOperation loggingMock ``` We tell the test to run a function with a mock using `runMock`. After that, we specify the actions that we expect to be run, and what they return, using the `:->` operator, inside of a `withActions` block. Finally, we run the function that we want to test, by passing the mocked component to it. # Functions that depend on multiple components Suppose that `importantOperation` depended on two, three, or whatever more components. The great stuff about Mockazo, is that you can chain as many `withActions` blocks as you want, passing the expected operations for each one of the mocks. Suppose that apart from the `Logging` component, we had another called `UserFetch`. We could also mock it in the same way we did with `Logging`, and add the expected operations too: ```haskell import qualified Mock.Logging as Logging import qualified Mock.UserFetch as UserFetch let loggingMock = Logging.mock let userFetchMock = UserFetch.mock -- ... somewhere our test framework runMock $ withActions [ Logging.LogInfo "info" :-> () , Logging.LogWarn "warn" :-> () , Logging.LogError "error" :-> () ] $ withActions [ UserFetch.Connect "localhost" :-> () , UserFetch.Fetch :-> User { name = "mike", password = "absolutely-encrypted" } ] $ importantOperation loggingMock userFetchMock ``` # Acknowledgements Mockazo is heavily inspired by [`monad-mock`](https://github.com/cjdev/monad-mock/). It wouldn't have been possible to create this package without it's existence. To all of the authors and contributors of `monad-mock`: ### **Thank you!**