PureScript Spec is a simple testing framework for Purescript, inspired by hspec. Use PureScript Spec to write synchronous and asynchronous tests using a simple DSL, combine with other testing tools, and generate test output in various formats.
Given that you already have a PureScript project setup, the first thing to do is installing purescript-spec
as a development dependency.
bower install --save-dev purescript-spec
The basic building block of spec writing is it
, which creates a spec with a spec body. Spec bodies have the type Aff Unit
, which is similar to the Effect
type, but with the addition of asynchronicity. When specs are run, they are considered successful, or passing, if the Aff computation does not result in an error. For more information, see purescript-aff.
In the following example we use pure unit
as a body, which does nothing. It will not throw an error, and the spec will always pass.
it "does nothing" $ pure unit
A more interesting test would assert something. Let’s check that addition works!
it "adds 1 and 1" do
1 + 1 `shouldEqual` 2
The shouldEqual
function, here used as an infix operator, takes two values and checks if they are equal. If not, it throws an error in the Aff monad, causing the spec to fail.
Specs can also be pending, which means that they are not testing anything yet - they are like placeholders. We use pending
to write a pending spec.
pending "calculates the answer to Life, the Universe and Everything"
Pending specs can also contain a spec body, just like with it
. The difference is that the body will be ignored. Pending spec bodies are used to give a hint what the spec should assert in the future. Use pending'
(note the '
at the end) to create a pending spec with a body.
pending' "calculates the answer to Life, the Universe and Everything" do
answerTo theUltimateQuestion `shouldBe` 42
To group multiple specs in a logically related group of specs, we use describe
. This creates a new spec which represents the named group.
describe "MyModule" do
it "..." do
...
it "..." do
...
it "..." do
...
Spec groups can be nested in multiple levels, creating a hierarchy of named groups.
describe "MyModule" $
describe "SubModule" $
describe "Database" do
it "..." do
...
it "..." do
...
it "..." do
...
Let’s look at an example of a complete spec program, with the needed imports and a proper main
function. The specs shown in the header image looks like this:
module Main where
import Prelude
import Data.Time.Duration (Milliseconds(..))
import Effect (Effect)
import Effect.Aff (launchAff_, delay)
import Test.Spec (pending, describe, it)
import Test.Spec.Assertions (shouldEqual)
import Test.Spec.Reporter.Console (consoleReporter)
import Test.Spec.Runner (runSpec)
main :: Effect Unit
main = launchAff_ $ runSpec [consoleReporter] do
describe "purescript-spec" do
describe "Attributes" do
it "awesome" do
let isAwesome = true
isAwesome `shouldEqual` true
pending "feature complete"
describe "Features" do
it "runs in NodeJS" $ pure unit
it "runs in the browser" $ pure unit
it "supports streaming reporters" $ pure unit
it "supports async specs" do
res <- delay (Milliseconds 100.0) $> "Alligator"
res `shouldEqual` "Alligator"
it "is PureScript 0.12.x compatible" $ pure unit
You can split specs into multiple files and combine them using regular monadic bind, e.g. with do
expressions.
baseSpecs = do
mathSpec
stringsSpec
arraySpec
...
This is often used to combine all specs into a single spec that can be passed to the test runner, if not using purescript-spec-discovery.
Sometimes you do not wish to run all specs. It might be that you are working on a certain feature, and only want to see the results for the relevant tests. It can also be that some spec takes a lot of time, and you wish to exclude it temporarily. By using itOnly
instead of the regular it
, the test runner includes only that spec.
describe "My API" do
itOnly "does feature X" ... -- only this spec will run
it "does things that takes a lot of time"
Similar to itOnly
, describeOnly
makes the runner include only that group.
describe "Module" do
describeOnly "Sub Module A" -- only this group will run
it "does feature X" ...
describe "Sub Module B"
it "does feature Y" ...
There is also focus
which can be used to select some specific group for execution
describe "Module" do
describe "Sub Module A"
it "does feature X" ...
focus $ describe "Sub Module B" do -- all tests passed to focus will be executed
it "does feature Y" ...
it "does feature Z" ...
describe "Sub Module C" do
it "does feature P" ...
You can use QuickCheck together with the purescript-spec-quickcheck adapter to get nice output formatting for QuickCheck tests.
You can use parallel
to mark specs for parallel execution. This is useful if you want to speed up your tests by not waiting for some async action to resolve. so if you have:
describe "delay" do
it "proc 1" do
delay $ Milliseconds 500.0
it "proc 2" do
delay $ Milliseconds 500.0
it "proc 3" do
delay $ Milliseconds 1000.0
It would take 2000 ms
to finish. But, by sticking in parallel
, it would take 1000 ms
:
- describe "delay" do
+ describe "delay" $ parallel do
NOTE that if you are logging things to console, by using parallel
order of log messages is less deterministic. For example if you had:
describe "delay" do
it "proc 1" do
log $ "start 1"
delay $ Milliseconds 500.0
log $ "end 1"
it "proc 2" do
log $ "start 2"
delay $ Milliseconds 500.0
log $ "end 2"
it "proc 3" do
log $ "start 3"
delay $ Milliseconds 1000.0
log $ "end 3"
you would see messages in this order:
start 1
end 1
start 2
end 2
start 3
end 3
but if you have used parallel
then messages will come in this order:
start 1
start 2
start 3
end 1
end 2
end 3
purescript-spec
itself is not providing any specific solution for this issue but you can take a look at /test/Test/Spec/HoistSpec.purs for some inspiration.
before_
runs a custom action before every spec item. For example, if you have an action flushDb
which flushes your database, you can run it before every spec item with:
main :: Spec Unit
main = before_ flushDb do
describe "/api/users/count" do
it "returns the number of users" do
post "/api/users/create" "name=Jay"
get "/api/users/count" `shouldReturn` 1
describe "when there are no users" do
it "returns 0" do
get "/api/users/count" `shouldReturn` 0
Similarly, after_
runs a custom action after every spec item:
main :: Spec Unit
main = after_ truncateDatabase do
describe "createUser" do
it "creates a new user" do
let eva = User (UserId 1) (Name "Eva")
createUser eva
getUser (UserId 1) `shouldReturn` eva
describe "countUsers" do
it "counts all registered users" do
countUsers `shouldReturn` 0
around_
is passed an action for each spec item so that it can perform whatever setup and teardown is necessary.
serveStubbedApi :: String -> Int -> Aff Server
stopServer :: Server -> Aff Unit
withStubbedApi :: Aff Unit -> Aff Unit
withStubbedApi action =
bracket (serveStubbedApi "localhost" 80)
stopServer
(const action)
main :: Spec Unit
main = around_ withStubbedApi do
describe "api client" do
it "should authenticate" do
c <- newClient (Just ("user", "pass"))
get c "/api/auth" `shouldReturn` status200
it "should allow anonymous access" do
c <- newClient Nothing
get c "/api/dogs" `shouldReturn` status200
Hooks support passing values to spec items (for example, if you wanted to open a database connection before each item and pass the connection in). This can be done with before
, around
and after
. Here’s an example for how to use around
:
openConnection :: Aff Connection
openConnection = ...
closeConnection :: Connection -> Aff Unit
closeConnection = ...
withDatabaseConnection :: (Connection -> Aff Unit) -> Aff Unit
withDatabaseConnection = bracket openConnection closeConnection
spec :: Spec Unit
spec = do
around withDatabaseConnection do
describe "createRecipe" do
it "creates a new recipe" $ \c -> do
let ingredients = [Eggs, Butter, Flour, Sugar]
createRecipe c (Recipe "Cake" ingredients)
getRecipe c "Cake" `shouldReturn` ingredients
Hooks support nesting too:
spec :: Spec Unit
spec = do
before (pure 1) $ after (\a -> a `shouldEqual` 1) do
it "before & after usage" \num -> do
num `shouldEqual` 1
beforeWith (\num -> num `shouldEqual` 1 *> pure true) do
it "beforeWith usage" \bool -> do
bool `shouldEqual` true
aroundWith (\computation bool -> bool `shouldEqual` true *> pure "fiz" >>= computation <* pure unit) do
it "aroundWith usage" \str -> do
str `shouldEqual` "fiz"
beforeWith (\num -> num `shouldEqual` 1 *> pure (show num)) do
it "beforeWith" \str -> do
str `shouldEqual` "1"
When you have a spec, you need a runner to actually run it and get the results. PureScript Spec comes with a NodeJS runner, runSpec
, which takes an array of reporters and a spec to run. What you get back is a test-running program of type Aff Unit
. The program can be run using Pulp.
pulp test
If you’re not using pulp, you can compile the test program using psc
. The following command compiles all PureScript modules in test
and src
.
psc -o output 'test/**/*.purs' 'src/**/*.purs'
After that has finished, you can run the test program using NodeJS.
NODE_PATH=output node -e "require('Test.Main').main();"
NOTE: A test program using Test.Spec.Runner.runSpec
cannot be browserified and run in the browser, it requires NodeJS. To run your tests in a browser, see Browser Testing below.
Reporters can be passed to the runner, e.g. runSpec [reporter1, ..., reporterN] spec
. Currently there are these reporters available:
consoleReporter
in Test.Spec.Reporter.Console
dotReporter
in Test.Spec.Reporter.Dot
specReporter
in Test.Spec.Reporter.Spec
tapReporter
in Test.Spec.Reporter.Tap
In addition to the regular runSpec
function, there is also runSpecT
, which also takes Config
record. also instead of Spec Unit
it takes SpecT Aff Unit m Unit
and returns m (Aff (Array (Tree Void Result)))
. if we specialize the m
to Identity
then code will look like this:
main = launchAff_ $ un Identity $ runSpecT testConfig [consoleReporter] mySpec
where
testConfig = { slow: 5000, timeout: Just 10000, exit: false }
If you are running your specs in an NodeJS environment, e.g. with pulp test
, you can automatically scan for spec modules using purescript-spec-discovery. Then your main
function can be as simple as:
main = discover "My\\.Package\\..*Spec" >>= runSpec [consoleReporter] >>> launchAff_
All modules matching the pattern, that has a spec :: Spec r ()
definition will be combined into a single large spec by discover
.
You can run tests in a browser environment, instead of NodeJS, using mocha
or karma
. For more information, see purescript-spec-mocha.
purescript-spec on Pursuit features version information and API documentation.
The source code is available on GitHub.
If you have any issues or possible improvements please file them as GitHub Issues. Pull requests are encouraged.