[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/buttercup 5db449f 025/340: Spies
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/buttercup 5db449f 025/340: Spies |
Date: |
Thu, 16 Dec 2021 14:58:58 -0500 (EST) |
branch: elpa/buttercup
commit 5db449f158c5ba46da6b66bdb1ac4f71a06b499f
Author: Jorgen Schaefer <contact@jorgenschaefer.de>
Commit: Jorgen Schaefer <contact@jorgenschaefer.de>
Spies
---
README.md | 37 ++++++++++++++++++++++++++
buttercup-test.el | 53 +++++++++++++++++++++++++++++++++++++
buttercup.el | 78 +++++++++++++++++++++++++++++++++++++++++++++++--------
3 files changed, 157 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 32c36d5..20062db 100644
--- a/README.md
+++ b/README.md
@@ -307,6 +307,43 @@ pending in results.
(it "can be declared with `it' but without a body"))
```
+## Spies
+
+Buttercup has test double functions called spies. While other
+frameworks call these mocks and similar, we call them spies, because
+their main job is to spy in on function calls. Also, Jasmine calls
+them spies, and so do we. A spy can stub any function and tracks calls
+to it and all arguments. A spy only exists in the `describe` or `it`
+block it is defined in, and will be removed after each spec. There are
+special matchers for interacting with spies. The
+`:to-have-been-called` matcher will return true if the spy was called
+at all. The `:to-have-been-called-with` matcher will return true if
+the argument list matches any of the recorded calls to the spy.
+
+```Lisp
+(describe "A spy"
+ (let (foo bar)
+ (before-each
+ (setf (symbol-function 'foo)
+ (lambda (value)
+ (setq bar value)))
+
+ (spy-on 'foo)
+
+ (foo 123)
+ (foo 456 "another param"))
+
+ (it "tracks that the spy was called"
+ (expect 'foo :to-have-been-called))
+
+ (it "tracks all arguments of its calls"
+ (expect 'foo :to-have-been-called-with 123)
+ (expect 'foo :to-have-been-called-with 456 "another param"))
+
+ (it "stops all execution on a function"
+ (expect bar :to-be nil))))
+```
+
## Test Runners
Evaluating `describe` forms just stores the suites. You need to use a
diff --git a/buttercup-test.el b/buttercup-test.el
index 1f33e54..cb071c4 100644
--- a/buttercup-test.el
+++ b/buttercup-test.el
@@ -324,3 +324,56 @@
"bla bla"
(lambda () (error "should not happen"))))
:not :to-throw)))
+
+;;;;;;;;;
+;;; Spies
+
+(defun test-function (a b)
+ (+ a b))
+
+(describe "The `spy-on' function"
+ (it "replaces a symbol's function slot"
+ (spy-on 'test-function)
+ (expect (test-function 1 2) :to-be nil))
+
+ (it "restores the old value after a spec run"
+ (expect (test-function 1 2) :to-equal 3)))
+
+(describe "The :to-have-been-called matcher"
+ (before-each
+ (spy-on 'test-function))
+
+ (it "returns false if the spy was not called"
+ (expect (buttercup--apply-matcher :to-have-been-called '(test-function))
+ :to-be
+ nil))
+
+ (it "returns true if the spy was called at all"
+ (test-function 1 2 3)
+ (expect (buttercup--apply-matcher :to-have-been-called '(test-function))
+ :to-be
+ t)))
+
+(describe "The :to-have-been-called-with matcher"
+ (before-each
+ (spy-on 'test-function))
+
+ (it "returns false if the spy was not called at all"
+ (expect (buttercup--apply-matcher
+ :to-have-been-called-with '(test-function 1 2 3))
+ :to-be
+ nil))
+
+ (it "returns false if the spy was called with different arguments"
+ (test-function 3 2 1)
+ (expect (buttercup--apply-matcher
+ :to-have-been-called-with '(test-function 1 2 3))
+ :to-be
+ nil))
+
+ (it "returns true if the spy was called with those arguments"
+ (test-function 1 2 3)
+ (expect (buttercup--apply-matcher
+ :to-have-been-called-with '(test-function 1 2 3))
+ :to-be
+ t)))
diff --git a/buttercup.el b/buttercup.el
index bb400ad..87c6d7f 100644
--- a/buttercup.el
+++ b/buttercup.el
@@ -362,6 +362,53 @@ A disabled spec is not run."
A disabled spec is not run."
nil)
+;;;;;;;;;
+;;; Spies
+
+(defvar buttercup--spy-calls (make-hash-table :test 'eq
+ :weakness 'key))
+
+(defun spy-on (symbol)
+ (letrec ((old-value (symbol-function symbol))
+ (new-value (lambda (&rest args)
+ (buttercup--spy-add-call new-value args)
+ nil)))
+ (fset symbol new-value)
+ (buttercup--add-cleanup (lambda () (fset symbol old-value)))))
+
+(defun buttercup--add-cleanup (function)
+ (if buttercup--current-suite
+ (buttercup-after-each function)
+ (setq buttercup--cleanup-forms
+ (append buttercup--cleanup-forms
+ (list function)))))
+
+(defun buttercup--spy-add-call (spy args)
+ (puthash spy
+ (append (buttercup--spy-calls spy)
+ (list args))
+ buttercup--spy-calls))
+
+(defun buttercup--spy-calls (spy)
+ (gethash spy buttercup--spy-calls))
+
+(buttercup-define-matcher :to-have-been-called (spy)
+ (let ((spy (if (symbolp spy)
+ (symbol-function spy)
+ spy)))
+ (if (buttercup--spy-calls spy)
+ t
+ nil)))
+
+(buttercup-define-matcher :to-have-been-called-with (spy &rest args)
+ (let* ((spy (if (symbolp spy)
+ (symbol-function spy)
+ spy))
+ (calls (buttercup--spy-calls spy)))
+ (if (member args calls)
+ t
+ nil)))
+
;; (let* ((buttercup--descriptions (cons description
;; buttercup--descriptions))
;; (debugger (lambda (&rest args)
@@ -417,7 +464,8 @@ Do not change the global value.")
(buttercup--before-each (append buttercup--before-each
(buttercup-suite-before-each suite)))
(buttercup--after-each (append (buttercup-suite-after-each suite)
- buttercup--after-each)))
+ buttercup--after-each))
+ (debug-on-error t))
(message "%s%s" indent (buttercup-suite-description suite))
(dolist (f (buttercup-suite-before-all suite))
(funcall f))
@@ -426,18 +474,27 @@ Do not change the global value.")
((buttercup-suite-p sub)
(buttercup-run-suite sub (1+ level)))
((buttercup-spec-p sub)
- (message "%s%s"
- (make-string (* 2 (1+ level)) ?\s)
- (buttercup-spec-description sub))
- (dolist (f buttercup--before-each)
- (funcall f))
- (funcall (buttercup-spec-function sub))
- (dolist (f buttercup--after-each)
- (funcall f)))))
+ (buttercup-run-spec sub (1+ level)))))
(dolist (f (buttercup-suite-after-all suite))
(funcall f))
(message "")))
+(defvar buttercup--cleanup-forms nil
+ "")
+
+(defun buttercup-run-spec (spec level)
+ (let ((buttercup--cleanup-forms nil))
+ (message "%s%s"
+ (make-string (* 2 level) ?\s)
+ (buttercup-spec-description spec))
+ (dolist (f buttercup--before-each)
+ (funcall f))
+ (funcall (buttercup-spec-function spec))
+ (dolist (f buttercup--cleanup-forms)
+ (funcall f))
+ (dolist (f buttercup--after-each)
+ (funcall f))))
+
(defun buttercup-run-at-point ()
(let ((buttercup-suites nil)
(lexical-binding t))
@@ -455,8 +512,7 @@ Do not change the global value.")
(with-current-buffer lisp-buffer
(insert code))))))
(with-current-buffer lisp-buffer
- (setq lexical-binding t
- debug-on-error t)
+ (setq lexical-binding t)
(eval-region (point-min)
(point-max)))
(buttercup-run)))
- [nongnu] elpa/buttercup 3483449 045/340: Typo fix., (continued)
- [nongnu] elpa/buttercup 3483449 045/340: Typo fix., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 84002e9 054/340: Add parent relations to specs and suites., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup a7d9813 010/340: Add cl-defstruct compatibility alias., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup fd70ccf 002/340: Specs are now structs., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup ba40ff8 027/340: Spies: :and-return-value, ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 20b87dd 031/340: Refactoring of spy-on to be more concise and DRY., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup bb31c85 043/340: Edebugable., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 5d369cd 007/340: Do not use emacs-snapshot from EVM, that's slow., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 07b1ee6 008/340: Add compatibility definition for define-error, ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 7cdb219 028/340: Spies: :and-call-fake, ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 5db449f 025/340: Spies,
ELPA Syncer <=
- [nongnu] elpa/buttercup d580e5c 011/340: Add build status badge., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 3637e3d 015/340: Add more extensive matcher tests to the README., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup a277b0e 022/340: Setup and teardown: before-each, after-each, before-all, after-all, ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 1882d6f 013/340: Comment cleanup. Also, make elisp lexically scoped., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup c5a9f26 003/340: Add first set of unit tests., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 36522b9 006/340: Add .travis.yml, ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup f44e65d 004/340: Unit tests for define-matcher and apply-matcher., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup d3cd12a 019/340: Tests for the buttercup-describe function, ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 3772ad5 014/340: Use eval-region instead of eval-buffer., ELPA Syncer, 2021/12/16
- [nongnu] elpa/buttercup 8aa87c8 026/340: Spy :and-call-through, ELPA Syncer, 2021/12/16