|
| 1 | +# purescript-mockfree |
| 2 | + |
| 3 | +`mockfree` is a purely-functional, strongly-typed mocking library for all PureScript programs that are defined by [Free](https://github.com/purescript/purescript-free) algebras. |
| 4 | + |
| 5 | +While a proof-of-concept for a [LambdaConf 2016 talk](http://github.com/lambdaconf/lambdaconf-2016-usa), the library is nonetheless completely usable, and demonstrates the power of modeling effectful computation through descriptive data structures. |
| 6 | + |
| 7 | +- [Module Documentation](docs/Test/Mock/Mockfree.md) |
| 8 | +- [Example](test/Main.purs) |
| 9 | + |
| 10 | +## Building |
| 11 | + |
| 12 | + bower install purescript-mockfree |
| 13 | + pulp build |
| 14 | + pulp test |
| 15 | + |
| 16 | +## Introduction |
| 17 | + |
| 18 | +Integration and system testing often require writing tests that perform *effects*, such as writing to a file system or connecting to a remote API. |
| 19 | + |
| 20 | +The problem with such tests is that they verify far more than the logic of your program: they test the reliability of third-party software and systems. |
| 21 | + |
| 22 | +As a result, integration and system tests are often indeterministic and error-prone. In addition, because these tests often run slowly and have complex dependencies, they are not as useful to developers as unit and property tests, which run very fast and have no dependencies. |
| 23 | + |
| 24 | +The industry has invented *mocking* as a solution to these problems. However, mocking usually sacrifices type safety, and requires a powerful dynamic programming language, or lots of hacks or code rewriting. |
| 25 | + |
| 26 | +With a purely-functional programming language such as PureScript, we have an alternative: `Free` programs. |
| 27 | + |
| 28 | +`Free` programs allow us to describe the effects of our program using data structures. These data structures can later be interpreted into effectful operations. However, they can also be interpreted into *non-effectful* operations that do *not* interact with external systems. |
| 29 | + |
| 30 | +If you program in this style, then `mockfree` will let you trivially test the logic of your programs with purely-functional, strongly-typed mocks, which can be parallelized, which run in-memory, and which are completely deterministic. |
| 31 | + |
| 32 | +Combined with a way of testing your program's final interpreters, functional mocking can totally transform the way you test functional code that has complex interactions with external systems. |
| 33 | + |
| 34 | +With `Free`, you can test final interpreters separately, one time, in their own library; and you can test your program logic exclusively through functional mocking. All tests become fast, deterministic, and completely self-contained! |
| 35 | + |
| 36 | +## Tutorial |
| 37 | + |
| 38 | +Let's define a console program with the following operations: |
| 39 | + |
| 40 | +```purescript |
| 41 | +data ConsoleF a |
| 42 | + = WriteLine (Op String Unit a) |
| 43 | + | ReadLine (Op Unit String a) |
| 44 | +``` |
| 45 | + |
| 46 | +Each operation `Op a b c` requires that we supply an `a` in order to get access to a `b`. |
| 47 | + |
| 48 | +For example, a "write line" operation requires that we supply a `String`, and gives us access to a `Unit` value (i.e. no information), while a "read line" operation requires that we supply a `Unit` (i.e. no information) to get access to a `String`. |
| 49 | + |
| 50 | +The `mockfree` library requires that we define polymorphic [prisms](http://github.com/purescript-contrib/purescript-profunctor-lenses) for each term in our operational algebra: |
| 51 | + |
| 52 | +```purescript |
| 53 | +_WriteLine :: OpPrism ConsoleF String Unit |
| 54 | +_ReadLine :: OpPrism ConsoleF Unit String |
| 55 | +``` |
| 56 | + |
| 57 | +While these prisms are required for `mockfree`, they are also useful for working with our algebra, and they can be shared with production code! |
| 58 | + |
| 59 | +Once the prisms have been defined, we can create a mock spec using some combination of `expect` (for read-write operations), `expectRead` (for read-only operations), and `expectWrite` (for write-only operations): |
| 60 | + |
| 61 | +```purescript |
| 62 | +mockSpec :: MockSpec ConsoleF |
| 63 | +mockSpec = do |
| 64 | + expectWrite _WriteLine (assertEquals "What is your name?") |
| 65 | + expectRead _ReadLine "World" |
| 66 | + expectWrite _WriteLine (assertEquals "Hello, World!") |
| 67 | +``` |
| 68 | + |
| 69 | +These specs can be run against a program using `runMock`: |
| 70 | + |
| 71 | +```purescript |
| 72 | +runMock mockSpec program :: Either String Unit |
| 73 | +``` |
| 74 | + |
| 75 | +where `program` is a program defined by sequential execution of the individual operations: |
| 76 | + |
| 77 | +```purescript |
| 78 | +program :: Free ConsoleF Unit |
| 79 | +program = do |
| 80 | + writeOp _WriteLine "What is your name?" |
| 81 | + name <- readOp _ReadLine |
| 82 | + writeOp _WriteLine ("Hello, " ++ name ++ "!") |
| 83 | +``` |
| 84 | + |
| 85 | +## Future Work |
| 86 | + |
| 87 | +There are several ways this library could be improved: |
| 88 | + |
| 89 | + 1. **Add support for branching programs.** Currently, the mock spec is a linear sequence of instructions. Ideally, it would be a tree that forks based on runtime values and allows alternatives. |
| 90 | + 2. **Add support for infinite mock specs.** Currently, the mock spec can only model finite, bounded programs. |
| 91 | + 3. **Factor out Assertion into a library.** Currently, there is no PureScript library for non-effectful assertions that generate nice, composable errors. |
| 92 | + 4. **Factor out the Op type & helpers into a library.** These could be useful in building `Free` programs, not just in testing them. |
0 commit comments