mockcat 0.5.4.0 → 0.5.5.0
raw patch · 6 files changed
+73/−22 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Test.MockCat.MockT: expectApplyTimes :: forall (m :: Type -> Type). MonadIO m => MockT m () -> Int -> MockT m ()
+ Test.MockCat.MockT: expectNever :: forall (m :: Type -> Type). MonadIO m => MockT m () -> MockT m ()
Files
- CHANGELOG.md +12/−0
- README-ja.md +3/−3
- README.md +21/−5
- mockcat.cabal +7/−1
- src/Test/MockCat/Mock.hs +5/−5
- src/Test/MockCat/MockT.hs +25/−8
CHANGELOG.md view
@@ -6,6 +6,18 @@ and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## 0.5.5.0+### Added+- Aliases `expectApplyTimes` and `expectNever` (preferred names) for pre-run expectation declarations.++### Documentation+- README (EN/JA) now recommends `expectApplyTimes` / `expectNever` over legacy `applyTimesIs` / `neverApply`.+- Clarified that `expectApplyTimes n` is the canonical form; `expectNever` is sugar for `expectApplyTimes 0`.++### Notes+- Legacy names remain exported for backward compatibility (no deprecation pragma yet). They may receive a soft deprecation notice in a future minor release after community feedback.++ ## 0.5.4.0 ### Added - Parallel execution support (verified counting under concurrency, stress tests).
README-ja.md view
@@ -211,14 +211,14 @@ writeFile outputPath content ``` -これは次のように`applyTimesIs`関数を使うことで実現できます。+これは次のように`expectApplyTimes`関数(旧: `applyTimesIs`)を使うことで実現できます。 ```haskell import Test.MockCat as M ... it "Read, and output files (contain ng word)" do result <- runMockT do _readFile ("input.txt" |> pack "contains ngWord")- _writeFile ("output.txt" |> M.any |> ()) `applyTimesIs` 0+ _writeFile ("output.txt" |> M.any |> ()) `expectApplyTimes` 0 operationProgram "input.txt" "output.txt" result `shouldBe` ()@@ -442,7 +442,7 @@ spec :: Spec spec = do it "named stub" do- f <- createNamedStubFun "named stub" $ "x" |> "y" |> True+ f <- createNamedStubFn "named stub" $ "x" |> "y" |> True f "x" "z" `shouldBe` True ``` 期待した引数に適用されなかった場合に出力されるエラーメッセージには、この名前が含まれるようになります。
README.md view
@@ -27,6 +27,22 @@ * Template Haskell helpers: `makeMock` to cut boilerplate for typeclasses * No hidden global state: tests stay isolated +### Tested Versions+mockcat is continuously tested in CI across these configurations (see `.github/workflows/Test.yml`):++| GHC | Cabal | OS |+|-----|-------|----|+| 9.2.8 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |+| 9.4.8 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |+| 9.6.3 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |+| 9.8.2 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |+| 9.10.1 | 3.10.3.0 / 3.12.1.0 | Ubuntu, macOS, Windows |++Notes:+* The `tested-with` stanza in the cabal file reflects the same GHC list.+* Other patch releases within the same minor series typically work; open an issue if you hit a snag.+* Older GHCs (< 9.2) are not targeted due to dependency bounds and modern TH/unliftio requirements.+ Designed to be something you can pick up to stub one or two spots and then forget again – more a handy extension of regular Haskell testing than a new framework. ### Quick Start@@ -71,7 +87,7 @@ Non‑invocation (two styles): ```haskell- _putKV ("x" |> "y" |> ()) `applyTimesIs` 0+ _putKV ("x" |> "y" |> ()) `expectApplyTimes` 0 -- (legacy name: applyTimesIs) neverApply $ _putKV ("x" |> "y" |> ()) ``` @@ -79,7 +95,7 @@ * Pipeline DSL: `a |> b |> returnValue` describes one expected application. * Matchers: `any`, `expect p label`, `expect_ p`, `$(expectByExpr [| predicate |])`. * Argument‑dependent returns: `onCase` or `cases [...]` (including differing values for same arg on later occurrences).-* Counting: `applyTimesIs`, `neverApply`, plus `shouldApplyTimes*` predicates on mocks.+* Counting: `expectApplyTimes` (legacy: `applyTimesIs`), `expectNever` (legacy: `neverApply`), plus `shouldApplyTimes*` predicates on mocks. * Ordering: `shouldApplyInOrder`, `shouldApplyInPartialOrder`. * Concurrency: Call recorded only when result evaluated; counting atomic. * Partial mocks: `makePartialMock` for generating only some methods.@@ -298,14 +314,14 @@ unless (pack "ngWord" `isInfixOf` content) $ writeFile outputPath content ```-This can be accomplished by using the `applyTimesIs` function as follows.+This can be accomplished by using the `expectApplyTimes` function (legacy name: `applyTimesIs`) as follows. ```haskell import Test.MockCat as M ... it "Read, and output files (contain ng word)" do result <- runMockT do _readFile ("input.txt" |> pack "contains ngWord")- _writeFile ("output.txt" |> M.any |> ()) `applyTimesIs` 0+ _writeFile ("output.txt" |> M.any |> ()) `expectApplyTimes` 0 operationProgram "input.txt" "output.txt" result `shouldBe` ()@@ -513,7 +529,7 @@ spec :: Spec spec = do it "named stub" do- f <- createNamedStubFun "named stub" $ "x" |> "y" |> True+ f <- createNamedStubFn "named stub" $ "x" |> "y" |> True f "x" "z" `shouldBe` True ``` The error message printed when a stub function is not applied to an expected argument will include this name.
mockcat.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: mockcat-version: 0.5.4.0+version: 0.5.5.0 synopsis: Mock library for test in Haskell. description: mockcat is a small mocking / stubbing DSL for Haskell tests. .@@ -30,6 +30,12 @@ license: MIT license-file: LICENSE build-type: Simple+tested-with:+ GHC == 9.2.8+ , GHC == 9.4.8+ , GHC == 9.6.3+ , GHC == 9.8.2+ , GHC == 9.10.1 extra-source-files: README.md README-ja.md
src/Test/MockCat/Mock.hs view
@@ -7,7 +7,7 @@ {-# HLINT ignore "Use null" #-} {-# OPTIONS_GHC -Wno-name-shadowing #-} -{- | This module provides bellow functions.+{- | This module provides the following functions. - Create mocks that can be stubbed and verified. @@ -527,13 +527,13 @@ doVerify :: (Eq a, Show a) => Maybe MockName -> AppliedParamsList a -> VerifyMatchType a -> Maybe VerifyFailed doVerify name list (MatchAny a) = do guard $ notElem a list- pure $ verifyFailedMesssage name list a+ pure $ verifyFailedMessage name list a doVerify name list (MatchAll a) = do guard $ Prelude.any (a /=) list- pure $ verifyFailedMesssage name list a+ pure $ verifyFailedMessage name list a -verifyFailedMesssage :: Show a => Maybe MockName -> AppliedParamsList a -> a -> VerifyFailed-verifyFailedMesssage name appliedParams expected =+verifyFailedMessage :: Show a => Maybe MockName -> AppliedParamsList a -> a -> VerifyFailed+verifyFailedMessage name appliedParams expected = VerifyFailed $ intercalate "\n"
src/Test/MockCat/MockT.hs view
@@ -5,7 +5,15 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# OPTIONS_GHC -Wno-name-shadowing #-}-module Test.MockCat.MockT (MockT(..), Definition(..), runMockT, applyTimesIs, neverApply, MonadMockDefs(..)) where+module Test.MockCat.MockT (+ MockT(..), Definition(..),+ runMockT,+ applyTimesIs,+ expectApplyTimes,+ neverApply,+ expectNever,+ MonadMockDefs(..)+ ) where import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Monad.Reader (ReaderT(..), runReaderT)@@ -28,7 +36,7 @@ appear in the verification log. * Order-sensitive checks reflect evaluation order, not necessarily wall-clock start order between threads.- * Perform verification (e.g. 'shouldApplyTimes', 'applyTimesIs') after all+ * Perform verification (e.g. 'shouldApplyTimes', 'expectApplyTimes') after all parallel work has completed; running it mid-flight may observe fewer calls simply because some results are still lazy. * Each 'runMockT' call uses a fresh IORef store; mocks are not shared across@@ -94,17 +102,18 @@ for_ defs (\(Definition _ m v) -> liftIO $ v m) pure a -{- | Specify how many times a stub function (or group of stub definitions) must- be applied (to /any/ arguments). The function patches the verification- predicate for the provided stub definitions so that, after 'runMockT'- completes, the total number of evaluated applications is checked.+{- | (Preferred: 'expectApplyTimes'; legacy: 'applyTimesIs')+ Specify how many times a stub function (or group of stub definitions) must+ be applied (to /any/ arguments). The function patches the verification+ predicate for the provided stub definitions so that, after 'runMockT'+ completes, the total number of evaluated applications is checked. Concurrency & laziness notes: * Counting is thread-safe: each evaluated application contributes exactly 1. * An application is only counted once its return value is evaluated; ensure your test forces (e.g. via @shouldBe@ or sequencing) all stub results before relying on the count.- * Invoke 'applyTimesIs' inside the 'runMockT' block during setup; do not+ * Invoke 'expectApplyTimes' (or legacy 'applyTimesIs') inside the 'runMockT' block during setup; do not call it after the block ends. @@@ -132,7 +141,7 @@ it "test runMockT" do result <- runMockT do _readFile ("input.txt" |> pack "content")- _writeFile ("output.text" |> pack "content" |> ()) `applyTimesIs` 0+ _writeFile ("output.text" |> pack "content" |> ()) `expectApplyTimes` 0 operationProgram "input.txt" "output.text" result `shouldBe` ()@@ -149,6 +158,10 @@ liftIO $ atomicModifyIORef' ref (\xs -> (xs ++ patched, ())) pure () +-- | Preferred clearer alias for 'applyTimesIs'. Use this in new code.+expectApplyTimes :: MonadIO m => MockT m () -> Int -> MockT m ()+expectApplyTimes = applyTimesIs+ neverApply :: MonadIO m => MockT m () -> MockT m () neverApply (MockT inner) = MockT $ ReaderT $ \ref -> do tmp <- liftIO $ newIORef []@@ -157,6 +170,10 @@ let patched = map (\(Definition s m _) -> Definition s m (`shouldApplyTimesToAnything` 0)) defs liftIO $ atomicModifyIORef' ref (\xs -> (xs ++ patched, ())) pure ()++-- | Alias for 'neverApply' providing naming symmetry with 'expectApplyTimes'.+expectNever :: MonadIO m => MockT m () -> MockT m ()+expectNever = neverApply instance MonadIO m => MonadMockDefs (MockT m) where addDefinition d = MockT $ ReaderT $ \ref -> liftIO $ atomicModifyIORef' ref (\xs -> (xs ++ [d], ()))