Icaro Motta 96d98c62ed
chore(tests)_: Facilitate writing event tests (#20424)
Introduces a new macro deftest-event to facilitate writing tests for event
handlers. Motivation came from the _problem of having to always extract event
handlers as vars in order to test them_.

Although the implementation of deftest-sub and deftest-event are similar,
deftest-sub is critically important because it guarantees changes in one
subscription can be caught by tests from all other related subscriptions in the
graph (reference: PR https://github.com/status-im/status-mobile/pull/14472).

This is not the case for the new deftest-event macro. deftest-event is
essentially a way of make testing events less ceremonial by not requiring event
handlers to be extracted to vars. But there are a few other small benefits:

- The macro uses re-frame and "finds" the event handler by computing the
  interceptor chain (except :do-fx), so in a way, the tests are covering a bit
  more ground.
- Slightly easier way to find event tests in the repo since you can just find
  references to deftest-event.
- Possibly slightly easier to maintain by devs because now event tests and sub
  tests are written in a similar fashion.
- Less code diff. Whether an event has a test or not, there's no var to
  add/remove.
- The dispatch function provided by the macro makes reading the tests easier
  over time. For example, when we read subscription tests, the Act section of
  the test is always the same (rf/sub [sub-name]). Similarly for events, the
  Act section is always (dispatch [event-id arg1 arg2]).
- Makes the re-frame code look more idiomatic because it's more common to define
  handlers as anonymous functions.

Downside: deftest-sub and deftest-event are relatively complicated macros.

Note: The test suite runs just as fast and clj-kondo can lint code within the
macro just as well.

Before:

```clojure
(deftest process-account-from-signal-test
  (testing "process account from signal"
    (let [cofx             {:db {:wallet {:accounts {}}}}
          effects          (events/process-account-from-signal cofx [raw-account])
          expected-effects {:db {:wallet {:accounts {address account}}}
                            :fx [[:dispatch [:wallet/get-wallet-token-for-account address]]
                                 [:dispatch
                                  [:wallet/request-new-collectibles-for-account-from-signal address]]
                                 [:dispatch [:wallet/check-recent-history-for-account address]]]}]
      (is (match? expected-effects effects)))))
```

After

```clojure
(h/deftest-event :wallet/process-account-from-signal
  [event-id dispatch]
  (let [expected-effects
        {:db {:wallet {:accounts {address account}}}
         :fx [[:dispatch [:wallet/get-wallet-token-for-account address]]
              [:dispatch [:wallet/request-new-collectibles-for-account-from-signal address]]
              [:dispatch [:wallet/check-recent-history-for-account address]]]}]
    (reset! rf-db/app-db {:wallet {:accounts {}}})
    (is (match? expected-effects (dispatch [event-id raw-account])))))
```
2024-06-13 22:03:02 -03:00

88 lines
6.0 KiB
Clojure

{:config-paths ["status-im"]
:output {:exclude-files ["src/user.cljs" "src/dev/user.cljs"]}
:lint-as {legacy.status-im.utils.views/defview clojure.core/defn
legacy.status-im.utils.views/letsubs clojure.core/let
reagent.core/with-let clojure.core/let
legacy.status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
utils.re-frame/defn clj-kondo.lint-as/def-catch-all
quo.react/with-deps-check clojure.core/fn
quo.previews.preview/list-comp clojure.core/for
legacy.status-im.utils.styles/def clojure.core/def
legacy.status-im.utils.styles/defn clojure.core/defn
test-helpers.unit/deftest-sub clojure.core/defn
test-helpers.unit/deftest-event clojure.core/defn
taoensso.tufte/defnp clojure.core/defn}
:linters {:case-duplicate-test {:level :error}
:case-quoted-test {:level :error}
:case-symbol-test {:level :error}
:clj-kondo-config {:level :error}
:cond-else {:level :error}
:condition-always-true {:level :error}
:conflicting-alias {:level :error}
:consistent-alias {:level :error
:aliases {clojure.set set
clojure.string string
clojure.walk walk
malli.core malli
malli.dev.pretty malli.pretty
malli.dev.virhe malli.virhe
malli.error malli.error
malli.generator malli.generator
malli.transform malli.transform
malli.util malli.util
promesa.core promesa
schema.core schema
status-im.feature-flags ff
taoensso.timbre log}}
:deprecated-namespace {:level :warning}
:docstring-blank {:level :error}
:equals-true {:level :error}
:inline-def {:level :error}
:invalid-arity {:skip-args [legacy.status-im.utils.fx/defn
utils.re-frame/defn]}
:loop-without-recur {:level :error}
:minus-one {:level :error}
:misplaced-docstring {:level :error}
:missing-body-in-when {:level :error}
:missing-clause-in-try {:level :error}
:missing-else-branch {:level :error}
:multiple-async-in-deftest {:level :error}
:not-empty? {:level :error}
:plus-one {:level :error}
:redundant-do {:level :error}
:redundant-let {:level :error}
:refer-all {:level :error}
:shadowed-fn-param {:level :error}
:shadowed-var {:level :error
;; We temporarily use :include to define an
;; allowlist of core Clojure vars. In the
;; future, as we progressively fix shadowed
;; vars, we should be able to delete this
;; option and lint all vars.
:exclude [type name]}
:self-requiring-namespace {:level :error}
:single-operand-comparison {:level :error}
:syntax {:level :error}
:unbound-destructuring-default {:level :error}
:underscore-in-namespace {:level :error}
:uninitialized-var {:level :error}
:unknown-require-option {:level :error}
:unreachable-code {:level :error}
:unresolved-namespace {:level :error}
;; TODO remove number when this is fixed
;; https://github.com/borkdude/clj-kondo/issues/867
:unresolved-symbol {:exclude [PersistentPriorityMap.EMPTY
number
legacy.status-im.test-helpers/restore-app-db]}
:unresolved-var {:level :error}
:unsorted-required-namespaces {:level :error}
:unused-alias {:level :warning}
:unused-binding {:level :error}
:unused-import {:level :error}
:unused-namespace {:level :error}
:unused-private-var {:level :error}
:unused-referred-var {:level :error}
:use {:level :error}}
:config-in-ns {mocks.js-dependencies {:linters {:clojure-lsp/unused-public-var {:level :off}}}}}