Coder Social home page Coder Social logo

metametadata / clj-fakes Goto Github PK

View Code? Open in Web Editor NEW
29.0 5.0 3.0 1.29 MB

An isolation framework for Clojure/ClojureScript.

Home Page: http://metametadata.github.io/clj-fakes/

License: MIT License

Clojure 99.23% Java 0.77%
fake stub monkey-patching mocking isolation-framework assertions tdd

clj-fakes's Introduction

clj-fakes is an isolation framework for Clojure/ClojureScript that makes creating test doubles much easier.

Clojars Project Gitter

Features

  • All test doubles are named "fakes" to simplify the terminology.
  • Fakes can be created for:
    • functions
    • instances of protocols and Java interfaces
  • "Nice" and "strict" protocol fakes are supported.
  • Monkey patching is supported to fake implicit dependencies.
  • Several functions are provided for asserting recorded calls.
  • Self-testing: automatically checks for unused fakes.
  • Informative error messages.
  • Test runner agnostic.
  • Arrange-Act-Assert style testing.

Installation

Requirements: Clojure 1.7.0+ and/or ClojureScript 1.10.238+.

Add this to your dependencies:

[clj-fakes "0.12.0"]

Require framework namespace in your unit test source file:

(ns unit.example
  (:require
    [clj-fakes.core :as f]    
    ; and/or:
    [clj-fakes.context :as fc]))

Cheat Sheet

Creating Faking Context

Explicit context:

(let [ctx (fc/context)]
  ; use clj-fakes.context API here
)

Implicit context:

(f/with-fakes
  ; use clj-fakes.core API here 
)
; on exit block will automatically unpatch all patched vars and execute self-tests

All the following examples are assumed to be used inside an implicit context.

Stubbing

Function Stub

(let [foo (f/fake [[1 2] "foo"
                   [3 4 5] "bar"])]
  (foo 1 2) ; => "foo"
  (foo 3 4 5) ; => "bar"
  (foo 100 200)) ; => raises "Unexpected args are passed into fake: (100 200) ..."

Method Stub

(let [cow (f/reify-fake p/AnimalProtocol
                        (sleep :fake [[] "zzz"]))]
  (p/sleep cow) ; => "zzz"
  (p/speak cow)) ; => undefined method exception

Nice Method Stub

(let [cow (f/reify-nice-fake p/AnimalProtocol)]
  (p/sleep cow) ; => FakeReturnValue
  (p/speak cow)) ; => FakeReturnValue 

Mocking

Function Mock

(let [foo (f/recorded-fake [[(f/arg integer?) (f/arg integer?)] #(+ %1 %2)])
      bar (f/recorded-fake [[(f/arg integer?) (f/arg integer?)] #(* %1 %2)])]
  (foo 1 2)
  (bar 5 6)
  (foo 7 8)
       
  (f/calls foo)
  ; => [{:args [1 2] :return-value 3}
  ;     {:args [7 8] :return-value 15}]

  (f/calls)
  ; => [[foo {:args [1 2] :return-value 3}]
  ;     [bar {:args [5 6] :return-value 30}]
  ;     [foo {:args [7 8] :return-value 15}]]
)

Method Mock

(let [cow (f/reify-fake p/AnimalProtocol
                        (speak :recorded-fake [f/any "moo"]))]
  (p/speak cow)
    
  (f/calls (f/method cow p/speak))) ; => [{:args ..., :return-value moo}]

Assertions

These functions return true or raise an exception and can be used only with recorded fakes.

Strictly One Call

(f/was-called-once foo [1 2])

(f/method-was-called-once p/speak cow ["Bob"])

At Least One Call

(f/was-called foo [1 2])

(f/method-was-called p/speak cow ["Bob"])

Strictly One Call Matched The Provided Args Matcher

(f/was-matched-once foo [1 2])

(f/method-was-matched-once p/speak cow ["Bob"])

No Calls

(f/was-not-called foo)

(f/method-was-not-called p/speak cow)

Calls In The Specified Order

(f/were-called-in-order
  foo [1 2 3]
  foo [(f/arg integer?)]
  bar [100 200]
  baz [300])

(f/methods-were-called-in-order
  p/speak cow []
  p/sleep cow []
  p/eat dog ["dog food" "water"]
  p/speak cow ["Bob"])

Monkey Patching

Caution: this feature is not thread-safe. Strongly consider avoiding it in Clojure code if you plan to someday run your tests concurrently.

Patch Function With Stub

(f/with-fakes
  (f/patch! #'funcs/sum (f/fake [[1 2] "foo"
                                 [3 4] "bar"]))
  (funcs/sum 1 2) ; => "foo"
  (funcs/sum 3 4)) ; => "bar"

; patching is reverted on exiting with-fakes block
(funcs/sum 1 2) ; => 3

Patch To Spy

(f/patch! #'funcs/sum (f/recorded-fake [f/any funcs/sum]))
(funcs/sum 1 2) ; => 3
(f/was-called funcs/sum [1 2]) ; => true

Self-tests

(f/with-fakes
  (f/fake [f/any nil]))
; => raises "Self-test: no call detected for: non-optional fake ..."

(f/with-fakes
  (f/recorded-fake))
; => raises "Self-test: no check performed on: recorded fake ..."

Documentation

More documentation can be found at the project site:

References

The API was mainly inspired by jMock and unittest.mock frameworks with design decisions loosely based on the "Fifteen things I look for in an Isolation framework" by Roy Osherove.

Some alternative frameworks with isolation capabilities:

Also take at look at the article "Isolating External Dependencies in Clojure" by Joseph Wilk.

For more detailed information about unit testing, TDD and test double patterns I'd recommend the books below:

  • "Test Driven Development: By Example" by Kent Beck
  • "Growing Object-Oriented Software, Guided by Tests" by Steve Freeman and Nat Pryce [site]
  • "xUnit Test Patterns: Refactoring Test Code" by Gerard Meszaros [site]

License

Copyright © 2015 Yuri Govorushchenko.

Released under an MIT license.

clj-fakes's People

Contributors

metametadata avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

clj-fakes's Issues

Clojurescript is a hard dependency

I’m using clj-fakes in a regular Clojure application and it’s great. That said, I haven’t been able to exclude the Clojurescript dependency:

Exception in thread "main" java.io.FileNotFoundException: Could not locate cljs/analyzer__init.class or cljs/analyzer.clj on classpath., compiling:(clj_fakes/reflection.clj:1:1)

Would it be possible to change clj-fakes.reflection to use reader conditionals? I’m happy to help out with a pull request, but I have roughly no experience with Clojurescript.

Calling fake function many times resulted in StackOverflowError

Hi there,

I've noticed an issue with faked function after calling it ~10000 times.
Here code to reproduce an issue

(f/with-fakes
  (let [foo (f/fake [[1] 3])]
    (dotimes [_ 10000]
      (foo 1))))

Seems like error appeared inside self-test-unused-fakes function because of too many remove wrappers from -mark-used

Would you like to see an MR with fix?

It's impossible to patch a variadic function with function calling an original implementation (ClojureScript only)

Steps
See unit.patch tests:

; TODO: fails in CLJS with:
; "TypeError: undefined is not a constructor (evaluating 'unit.fixtures.functions.variadic.cljs$core$IFn$_invoke$arity$1(arguments[0])')"
#?(:clj
   (u/-deftest
     "variadic function can be patched with non-variadic function which calls original function"
     (f/with-fakes
       (f/patch! #'funcs/variadic (fn my-sum
                                    [x]
                                    ((f/original-val #'funcs/variadic) x)))

       (is (= "[a]" (funcs/variadic 100))))))

; TODO: fails in CLJS with "RangeError: Maximum call stack size exceeded."
#?(:clj
   (u/-deftest
     "variadic function can be patched with variadic function which calls original function"
     (f/with-fakes
       (let [original-variadic funcs/variadic]
         (f/patch! #'funcs/variadic (fn my-variadic
                                      ([] (original-variadic))
                                      ([a] ((f/original-val #'funcs/variadic) a))
                                      ([_ _] 2)
                                      ([_ _ _] 3)
                                      ([_ _ _ & _] :etc)))

         (is (= "[]" (funcs/variadic)))
         (is (= "[a]" (funcs/variadic 1)))
         (is (= 2 (funcs/variadic 1 2)))
         (is (= 3 (funcs/variadic 1 2 3)))
         (is (= :etc (funcs/variadic 1 2 3 4 5 6 7)))))))

Cause

Bug appears because of the way variadic functions are compiled to JS code. Example:

(defn variadic
      ([] "[]")
      ([_] "[a]"))

(let [original-variadic variadic
      new-variadic (fn my-variadic
                     []
                     (original-variadic))]
  (set! variadic new-variadic)
  (variadic))

compiles to this JS code:

cljs.user.variadic = (function cljs$user$variadic(var_args){
    ....

    var G__24 = args.length;
    switch (G__24) {
      case (0):
        return cljs.user.variadic.cljs$core$IFn$_invoke$arity$0();
        break;
      case (1):
        return cljs.user.variadic.cljs$core$IFn$_invoke$arity$1((arguments[(0)]));
        break;
      default:
        throw (new Error([cljs.core.str("Invalid arity: "),cljs.core.str(args.length)].join('')));
    }
  });

  cljs.user.variadic.cljs$core$IFn$_invoke$arity$0 = (function (){...});
  cljs.user.variadic.cljs$core$IFn$_invoke$arity$1 = (function (_){...});

  cljs.user.variadic.cljs$lang$maxFixedArity = (1);
  var original_variadic_29 = cljs.user.variadic;
  var new_variadic_30 = ((function (original_variadic_29){
    return (function cljs$user$my_variadic(){
      return original_variadic_29.call(null);
    });
  })(original_variadic_29));

  cljs.user.variadic = new_variadic_30;
  cljs.user.variadic.call(null);

As you can see, cljs.user.variadic = new_variadic_30; will erase cljs.user.variadic.cljs$core$IFn$_invoke$arity$0 leading to a bug.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.