项目作者: igrishaev

项目描述 :
Clojure mocking library
高级语言: Clojure
项目地址: git://github.com/igrishaev/mockery.git
创建时间: 2017-03-27T07:25:23Z
项目社区:https://github.com/igrishaev/mockery

开源协议:Eclipse Public License 1.0

下载


Mockery

Mockery is a simple and lightweight library to mock Clojure functions. It is
inspired by Python’s mock tool initially written
by Michael Foord.

Installation

Add it to your dependencies:

[mockery "0.1.4"]

Mockery is used mostly for unit tests so it’s better to put it into the :test
project’s profile but not the global one.

Why?

Imagine you have a function that fetches data from any remote service, say
Google or Twitter. On your dev machine, you don’t have valid credentials or even
cannot access a service due to network configuration.

How to write tests for such code? Moreover, how to ensure you application
handles an incorrect HTTP response?

  1. HTTP/1.1 403 OK
  2. {"error": "wrong credentials"}

Or even non-JSON data (standard Nginx page):

  1. HTTP/1.1 504 Gateway Timeout
  2. <html><body><p>Gateway Timeout</p></body></html>

Will your application deal with such a response properly without crushing with
500 error? If you think it will, how could you guarantee that?

Now imagine you have modern micro-service architecture where each action
requires collecting data across 3 internal web-services making API calls and to
compose a final result.

Mockery helps to imitate such unusual behaviour and write unit tests that cover
all the cases were mentioned.

Example

Mockery provides a with-mock macros. It substitutes a function with a
temporary one while the code block is being executed. Inside the code block, you
may call that function without performing unwanted I/O.

Under the hood, it relies on the standard
Clojure clojure.core/with-redefs-fn function. The changes are
visible across all the threads while macros works.

Quick example:

  1. (with-mock mock
  2. {:target ::test-fn ;; your function to mock
  3. :return 42} ;; the result
  4. (test-fn 1 2 3)) ;; will return 42

The first parameter, mock in our example, is a variable name to bind a mock
object to. The second one is a map of options. The :target is the only
required key that should be a full-qualified keyword or symbol. For example,
:clojure.string/join or 'clojure.string/join. If you’d like to point a
function located at the same namespace, use double colon syntax as well:
::my-func (expands into something like :your.current.ns/my-func).

Features

Mockery works with the built-tin unit test framework as well:

  1. (defn mock-google [f]
  2. (with-mock _
  3. {:target :some.project/get-geo-point ;; makes API call to Google
  4. :return {:lat 14.2345235
  5. :lng 52.523513}} ;; what your function should return instead
  6. (f)))
  7. (use-fixtures
  8. :each
  9. mock-google)
  10. (deftest ...)

During the test, every (get-geo-point ...) call will return just the data you
specified without network communication.

:return value could be a function that will be called to produce further
result.

  1. (with-mock _
  2. {:target :myapp.google/get-geo-point
  3. :return (fn [& _] 100500)}
  4. (myapp.google/get-geo-point)) ;; returns 100500

The library imitates throwing bith Java and Slingshot exceptions:

  1. (with-mock mock
  2. {:target ::test-fn
  3. :throw (Exception. "boom")}
  4. (try
  5. (test-fn 1)
  6. (catch Exception e
  7. (println e)))) ;; it was thrown
  8. (with-mock mock
  9. {:target ::test-fn
  10. :throw {:type :domain/error ;; slingshot map
  11. :data "boom"}}
  12. (try+
  13. (test-fn 1)
  14. (catch [:type :domain/error] data
  15. (println data)))) ;; process data map here

Add side effects (prints, logs) passing a :side-effect key with a function
without arguments:

  1. (with-mock mock
  2. {:target ::my-func
  3. :return 42
  4. :side-effect #(println "Hello!")}
  5. (my-func 1))
  6. ;; in addition to the result, "Hello!" will appear.

Mockery helps to ensure the function was called the exact number of times with
proper arguments. The mock atom holds a state that changes while you call the
mocked function. It accumulates its arguments and the number of calls:

  1. (with-mock mock
  2. {:target ::test-fn
  3. :return 42}
  4. (test-fn 1)
  5. (test-fn 1 2)
  6. (test-fn 1 2 3)
  7. @mock)
  8. ;; returns (some fields are skipped)
  9. {:called? true
  10. :call-count 3
  11. :call-args '(1 2 3) ;; the last args
  12. :call-args-list '[(1) (1 2) (1 2 3)] ;; args history
  13. :return 42 ;; the last result
  14. :return-list [42 42 42] ;; the entire result history
  15. }

You may mock multiple functions at once using with-mocks macro:

  1. (with-mocks
  2. [foo {:target ::test-fn}
  3. bar {:target ::test-fn-2}]
  4. (test-fn 1)
  5. (test-fn-2 1 2)
  6. (is (= @foo
  7. {:called? true
  8. :call-count 1
  9. :call-args '(1)
  10. :call-args-list '[(1)]
  11. :target ::test-fn}))
  12. (is (= @bar
  13. {:called? true
  14. :call-count 1
  15. :call-args '(1 2)
  16. :call-args-list '[(1 2)]
  17. :target ::test-fn-2})))

Other

For further reading, check out Mockery documentation
and unit tests. Feel free to submit your issues/suggestions.

Mockery was written by Ivan Grishaev, 2018.