mockcat-1.0.0.0: test/Test/MockCat/ExampleSpec.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-simplifiable-class-constraints #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Test.MockCat.ExampleSpec (spec) where
import Test.Hspec
import Test.MockCat
import Prelude hiding (writeFile, readFile, and, any, not, or)
import GHC.IO (evaluate)
import Data.Text hiding (any)
import Control.Monad.Trans.Maybe (MaybeT, runMaybeT)
import Test.MockCat.SharedSpecDefs
makeAutoLiftMock [t|FileOperation|]
operationProgram ::
FileOperation m =>
FilePath ->
FilePath ->
m ()
operationProgram inputPath outputPath = do
content <- readFile inputPath
writeFile outputPath content
echoProgram :: Teletype m => m ()
echoProgram = do
i <- readTTY
case i of
"" -> pure ()
_ -> writeTTY i >> echoProgram
makeMock [t|Teletype|]
class Monad m => StrictTest m where
strictFunc :: String -> m ()
instance StrictTest IO where
strictFunc _ = pure ()
makeMock [t|StrictTest|]
spec :: Spec
spec = do
it "Default makeMock is strict (requires pure)" do
r <- runMockT do
_strictFunc $ "arg" ~> pure @IO ()
strictFunc "arg"
r `shouldBe` ()
it "function arg" do
let
f :: String -> String -> String
f a b = a <> b
-- Example of explicit type application using any @type
stub <- mock $ "a" ~> any @(String -> String -> String) ~> True
stub "a" f `shouldBe` True
it "function arg (inferred)" do
let
f :: String -> String -> String
f a b = a <> b
-- Example of relying on type inference
stub <- mock $ "a" ~> any ~> True
stub "a" f `shouldBe` True
it "function arg2" do
let
-- Do not allow optimization to remove duplicates.
{-# NOINLINE f #-}
f :: String -> String -> String
f a b = a <> b
{-# NOINLINE g #-}
g :: String -> String -> String
g a b = a <> b
stub <- mock $ "a" ~> f ~> g ~> True
-- Verify that calling with wrong function order raises an error
evaluate (stub "a" g f) `shouldThrow` anyErrorCall
it "echo1" do
result <- runMockT do
_readTTY $ pure @IO ""
echoProgram
result `shouldBe` ()
it "echo2" do
result <- runMockT do
_readTTY $ do
onCase $ pure @IO "a"
onCase $ pure @IO ""
_writeTTY $ "a" ~> pure @IO ()
echoProgram
result `shouldBe` ()
it "echo3" do
result <- runMockT do
_readTTY $ casesIO ["a", ""]
_writeTTY $ "a" ~> pure @IO ()
echoProgram
result `shouldBe` ()
it "echo4" do
result <- runMockT do
_readTTY $ cases [ pure @IO "a", pure @IO "" ]
_writeTTY $ "a" ~> pure @IO ()
echoProgram
result `shouldBe` ()
it "read & write" do
result <- runMockT do
_readFile $ "input.txt" ~> pack "Content"
_writeFile $ "output.text" ~> pack "Content" ~> ()
operationProgram "input.txt" "output.text"
result `shouldBe` ()
it "stub" do
-- create a stub function
stubFn <- mock $ "value" ~> True
-- assert
stubFn "value" `shouldBe` True
it "stub & verify" do
-- create a mock
mockFn <- mock $ "value" ~> True
-- assert
mockFn "value" `shouldBe` True
it "how to use" do
f <- mock $ "param1" ~> "param2" ~> pure @IO ()
actual <- f "param1" "param2"
actual `shouldBe` ()
it "named stub" do
f <- mock (label "named stub") $ "x" ~> "y" ~> True
f "x" "y" `shouldBe` True
it "mock returns monadic stub (IO)" do
f <- mock $ "a" ~> (11 :: Int) ~> pure @IO False
f "a" (11 :: Int) `shouldReturn` False
it "mock returns monadic stub (MaybeT)" do
mm <- runMaybeT do
-- create a mock inside MaybeT
mock $ True ~> pure @(MaybeT IO) False
case mm of
Nothing -> expectationFailure "mock returned Nothing"
Just f -> do
-- f :: Bool -> MaybeT IO Bool
res <- runMaybeT $ f True
res `shouldBe` Just False
it "named mock" do
f <- mock (label "mock") $ "value" ~> "a" ~> True
f "value" "a" `shouldBe` True
it "stub function" do
f <- mock $ "value" ~> True
f "value" `shouldBe` True
it "any" do
f <- mock $ any ~> "return value"
f "something" `shouldBe` "return value"
it "expect" do
-- Explicit type annotations like (4 :: Int) are required because the polymorphism
-- of numeric literals and operators makes the type of Param ambiguous.
f <- mock $ expect (> (5 :: Int)) "> 5" ~> "return value"
f 6 `shouldBe` "return value"
it "expect_" do
f <- mock $ expect_ (> (5 :: Int)) ~> "return value"
f 6 `shouldBe` "return value"
it "expectByExpr" do
f <- mock $ $(expectByExpr [|(> (5 :: Int))|]) ~> "return value"
f 6 `shouldBe` "return value"
it "multi" do
f <- mock do
onCase $ "a" ~> "return x"
onCase $ "b" ~> "return y"
f "a" `shouldBe` "return x"
f "b" `shouldBe` "return y"
it "Return different values for the same argument" do
f <- mock $ do
onCase $ "arg" ~> "x"
onCase $ "arg" ~> "y"
-- Do not allow optimization to remove duplicates.
v1 <- evaluate $ f "arg"
v2 <- evaluate $ f "arg"
v3 <- evaluate $ f "arg"
v1 `shouldBe` "x"
v2 `shouldBe` "y"
v3 `shouldBe` "y" -- After the second time, “y” is returned.