mount

onetom 2017-07-12T02:57:26.099295Z

@tolitius The question was really about the function signatures of notify, query & send because those are the functions i would write tests for. I'm not sure what do u suggest because u went from having • a Database protocol with one query method with an implementation accessible as (:db customers) • an EmailSender protocol with one send method with an implementation accessible as (:email customers) to • an app.db state doing the same as (query (:db customers)) • an app.email state doing the same as (send (:email customers))

onetom 2017-07-12T02:58:02.105696Z

So let me ask more specifically. Given this implementation:

(in-ns 'app.db)
(defstate db
  :start (db-lib/connect "db-uri")
  :stop (db-lib/disconnect db))

(defn query [name]
  (db-lib/query db some-query name))

; ----------------------------------------

(in-ns 'app.emailer)
(defstate emailer ...)
(defn send [addr msg]
  (email-lib/send emailer addr msg))

; ----------------------------------------

(in-ns 'app.customers)
(require 'app.db)
(require 'app.emailer)

(defn notify [name message]
  (app.emailer/send 
    (app.db/query [name])
    message))

onetom 2017-07-12T02:58:32.111375Z

which could be tested as:

(in-ns 'app.customers-test)
(require 'app.customers)
; The test should be aware what other states does the System Under Test depends on
; which would be nice to keep as an implementation detail to make tests less rigid
(require 'app.db)
(require 'app.emailer)

(defn mount-start-stop [test]
  (mount/stop)
  (mount/start-with
    {#'app.db/db           (comment "some mock db implementation acceptable to db-lib/query")
     #'app.emailer/emailer (comment "some mock emailer implementation acceptable to email-lib/send")})
  (try (test)
    (finally (mount/stop))))

(use-fixtures :each mount-start-stop)

(deftest notify-test
  ; GIVEN
  (db-lib/insert app.db/db {:name "Joe" :address "<mailto:joe@address.com|joe@address.com>"})
  ; OR using some domain function instead?
  (app.db/add-person "Joe" "<mailto:joe@address.com|joe@address.com>")

  ; WHEN
  (let [notify-result (app.customers/notify "Joe" "Hello Joe")]

    ; THEN function return value
    (is (= :ok notify-result))

    ; AND other state changes are
    (let [email (first-sent-email app.emailer/emailer)]
      (is (= "<mailto:joe@address.com|joe@address.com>" (:to email)))
      (is (= "Hello Joe" (:body email))))))

(deftest other-notify-test
  ; GIVEN
  "some other db content"
  ...)

onetom 2017-07-12T02:58:39.112625Z

I think it's important to show a complete realistic test to really evaluate the consequences of an approach.

onetom 2017-07-12T02:58:48.114098Z

but based on your example, I think what u wanted to say was

(in-ns 'app.customers-test)
(require 'app.customers)
; Still need to know the dependent states
(require 'app.db)
(require 'app.emailer)

(mount/start-with
  {#'app.db/query     (fn [name] (println "querying DB with" name))
   #'app.emailer/send (fn [addr msg] (println "sending email to" addr "body:" msg))})

onetom 2017-07-12T02:59:03.116860Z

If that's correct then a full example would look like this:

(in-ns 'app.customers-test)
(require 'app.customers)
; Still need to know the dependent states
(require 'app.db)
(require 'app.emailer)

(defn ensure-mount-stop [test]
  (mount/stop)
  (try (test)
    (finally (mount/stop))))

(use-fixtures :each ensure-mount-stop)

(deftest notify-test
  ; GIVEN
  (let [emails (atom [])]
    (mount/start-with
      {#'app.db/query     (fn [name] 
                            (if (= "Joe" name)
                                "<mailto:joe@address.com|joe@address.com>" 
                                (str "&lt;address for '" name "''&gt;")))
       #'app.emailer/send (fn [addr msg]
                            (swap! emails conj [addr msg]))})

    ; WHEN
    (let [notify-result (app.customer/notify "Joe" "Hello Joe")]

      ; THEN function return value
      (is (= :ok notify-result))

      ; AND other state changes are
      (is (= 1 (count @emails)))
      (is (= ["<mailto:joe@address.com|joe@address.com>" "Hello Joe"]
             (first @emails))))))

(deftest other-notify-test
  ; GIVEN all over again just slightly differently?
  (let [emails (atom [])]
    (mount/start-with
      {#'app.db/query     (fn [name] 
                            (if (= "Mary" name)
                                "<mailto:mary@address.com|mary@address.com>" 
                                (str "&lt;address for '" name "''&gt;")))
       #'app.emailer/send (fn [addr msg]
                            (swap! emails conj [addr msg]))})
  ...

onetom 2017-07-12T02:59:12.118561Z

What i miss here is some standardized way of setting up the substituted states. some kind of a unified way to describing preconditions to the test. I also feel like there should be a (reset! emails []) in there somewhere but since it's in just local scope now, not inside a state, it works fine this way too. I've also noticed that the two approaches match the 2 major approaches of testing 1. using mocks (when states are some kind data/objects) 2. using stubs (when states are functions)

onetom 2017-07-12T02:59:27.121224Z

For the sake of completeness lets also see how the implementation would look like for the test above:

(in-ns 'app.db)
(defstate db
  :start (db-lib/connect "db-uri")
  :stop (db-lib/disconnect db))

(defstate query
  :start (fn query [name]
           (db-lib/query db some-query name))
  ; :stop is not necessary because this state is a pure function which is stateless...
  ; which is kinda confusing to define it as a state just because it depends on a state...
  )

; ----------------------------------------

(in-ns 'app.emailer)
(defstate emailer ...)
(defstate send 
  :start (fn send [addr msg]
           (email-lib/send emailer addr msg))
  ; :stop is not necessary for the same reasons as explained above
  )

; ----------------------------------------

(in-ns 'app.customers)
(require 'app.db)
(require 'app.emailer)

(defn notify [name message]
  (app.emailer/send 
    (app.db/query [name])
    message))

; OR wait a second!!!
; the functions it uses are defined as states so this function also depends on states,
; hence this should be defined as state too, no?
; 
; otherwise it might see older functions referring to stale, already stopped state?...
; okay, here is where im not sure and find it slightly magical :/

(defstate notify
  :start (fn notify [name message]
          (app.emailer/send 
            (app.db/query [name])
            message))

onetom 2017-07-12T02:59:31.122053Z

If I must define notify using defstate in order to have a consistent test structure, where I don't have to handle "function states" differently from "value/object/mutable states", then it smells a bit like a whole-app buyin to me...

onetom 2017-07-12T03:00:10.130212Z

So my question is Q1. Which approach do you promote and why? 1st approach is wrapping objects into states which are substituted in tests 2nd approach is packaging not just the objects but the functions (which are the APIs of our modules) into states so such API can be substituted during tests (in both cases the functions implicitly reference the states holding objects which are required by their underlying libraries. there is actually an approach where we make these dependencies of our API functions explicit by requiring them as arguments, just like how most underlying libraries do so too)

onetom 2017-07-12T03:01:15.143048Z

Q2. Am I correct to say that the difference between the 2 approaches whether the tests for them must be written in a mockist or stubbist style?

onetom 2017-07-12T03:06:27.198518Z

Q3. If you recommend the second approach, then should notify be a function or a state?

onetom 2017-07-12T03:08:01.214573Z

I think "it can be both" is not an acceptable answer, because it affects how tests should be written for them and that introduces such inconsistency which i feel is not inherent to the problem (of combining states and state using functions)

dm3 2017-07-12T07:22:02.131072Z

interesting example. I find myself doing the following when using Mount: * states are things which have a clearly defined lifecycle - a DB connection, a stream of data, an atom of things * states are hidden in the declaring namespace. The API of the namespace are the functions operating on states. * tests test the API directly by mocking the functions

(testing ...
  (let [!results (atom [])]
     (with-redefs [my.notifications/notify! #(swap! !results conj %)]
       (my.email/send-with-notifications "IMPORTANT")
       (is (= ["IMPORTANT"] @!results))))
of course the mocking is usually hidden away behind a few functions/macros.

dm3 2017-07-12T07:23:47.162360Z

(this is a degenerate test that I would not consider writing in real life)

dm3 2017-07-12T07:26:05.204220Z

start-with has more uses when the namespace API is a shallow wrapper or the test uses an actual testable implementation of the resource captured in the state, like a mem datomic connection.

dm3 2017-07-12T07:27:24.228477Z

@onetom ^

onetom 2017-07-12T07:44:16.563149Z

@dm3 thanks. interesting. i thought a major point of using mount is to avoid with-redefs so im even more puzzled now 🙂

dm3 2017-07-12T08:12:21.138913Z

why would you try to avoid with-redefs?

dm3 2017-07-12T08:12:36.144435Z

if that’s your API?

onetom 2017-07-12T10:18:34.971372Z

@dm3 https://youtu.be/13cmHf_kt-Q?t=1626 watch it until 29:16 he explains quite precisely what kind of issues will u have if you have any async code within your tested system and some other considerations

dm3 2017-07-12T10:41:04.399977Z

I know all of that

dm3 2017-07-12T10:41:32.408578Z

never had to deal with issues like that in tests though

dm3 2017-07-12T10:41:52.414563Z

maybe because I’m already aware of them

dm3 2017-07-12T10:58:22.719569Z

I can see how that could lead to problems if running tests in parallel though. Haven’t had the need to do that yet

dm3 2017-07-12T11:19:25.099392Z

you’d have to use something like Boot’s pods

onetom 2017-07-12T14:29:51.425419Z

using pods would be even more ceremony and distraction from the core logic of what you are trying to achieve...

onetom 2017-07-12T14:35:38.660606Z

and i don't think the main issue with with-redefs is parallel test running but more like having any kind of asynchrony mixed into the program. an rather realistic example is sending an email out. would you wait for an amazon ses api call to complete during a user registration step before you respond to the user regarding his registration being successful?

onetom 2017-07-12T14:37:00.714488Z

if yes, it slows the response down, if no, then how would u know from your test when has that call finished? if u provide a mock ses component which is synchronous, then you are cheating because you are testing a system with different semantics...

dm3 2017-07-12T14:43:05.957572Z

hmm, are you talking about testing code like

(defn register [email name]
  (db/save! {:email email, :name name})
  (email/send! {:to email, :text (str "hi " name)}))
? I’m not sure I understand the issue… The code calling email/send! doesn’t know whether that’s sync or async. What sort of tests are you thinking about?

dm3 2017-07-12T14:44:20.007625Z

if email/send! returns a promise then the mocked function would also return a promise

onetom 2017-07-12T14:44:30.014646Z

yes, email/send! might just return a promise and that might not even be the return value of the register function, so from a test you would need to be able to wait for the email to arrive

dm3 2017-07-12T14:45:57.074093Z

I guess I don’t understand why you need to wait for an email to arrive if you mock it out

onetom 2017-07-12T14:46:19.089589Z

imagine you are writing a route handler or a castra endpoint. those functions should return a http response (roughly)

onetom 2017-07-12T14:47:18.129935Z

so you wont have access to the promise from the tests

dm3 2017-07-12T14:47:55.154564Z

are we talking about a unit test for a e.g. route handler where you mock out all dependencies?

onetom 2017-07-12T14:49:37.224100Z

okay, so i guess that's the source of the misunderstanding. what is a unit test? to me a unit in this case is the register function and i want to test that...

onetom 2017-07-12T14:49:58.238829Z

preferably with the least amount of modification compared to the production setup

dm3 2017-07-12T14:50:35.263533Z

ok, so a unit test would mock out everything and just test the unit logic.

onetom 2017-07-12T14:50:55.277292Z

so most of my code should run behind it

dm3 2017-07-12T14:51:16.291839Z

you mean like stuff inside db/save! and email/send!?

onetom 2017-07-12T14:51:23.296416Z

yes

dm3 2017-07-12T14:51:33.302940Z

ok, that’s not a unit test in my book anymore 🙂

onetom 2017-07-12T14:51:40.307906Z

because if i mock those out then im totally coupling my test to the implementation details

dm3 2017-07-12T14:51:44.309994Z

yep

onetom 2017-07-12T14:52:00.321007Z

which is rather pretty fucking useless in my book 😜

dm3 2017-07-12T14:52:03.322575Z

that’s why you wouldn’t write a unit test for that fn in real world

onetom 2017-07-12T14:52:20.334069Z

then we agree 🙂

dm3 2017-07-12T14:52:38.346087Z

yeah, but I guess we were working towards some example where with-redefs doesn’t work

dm3 2017-07-12T14:53:02.362155Z

I use it to mock out APIs, like email/send! or db/save!

onetom 2017-07-12T14:53:04.364229Z

so how would you call such a test where you still let code behind db/save! and email/send! run?

dm3 2017-07-12T14:53:45.393416Z

I’d have to with-states the actual things inside defstate with testable implementations

onetom 2017-07-12T14:54:06.407510Z

we are going off-topic though... maybe #testing would be a better channel for such discussion 🙂

onetom 2017-07-12T14:54:30.424167Z

i agree with that statement too

onetom 2017-07-12T14:55:19.457655Z

however the "cheating" enters the equation when we start to use defstate on the API functions around the stateful constructs of our program...

onetom 2017-07-12T14:56:09.492118Z

i think the problem is that we don't have a clear, consistent, shared vocabulary around the topic

dm3 2017-07-12T14:57:36.553185Z

in my world you either mock the API function for a unit test or use a testable stateful thing for an integration test

dm3 2017-07-12T14:58:33.593324Z

but I must confess that I do less testing than I probably should

onetom 2017-07-12T14:59:47.643618Z

dm3: i would actually call the "testable stateful thing" case mocking and the fake function case stubbing

onetom 2017-07-12T15:00:52.690765Z

and those two styles was i trying to show in my examples earlier

dm3 2017-07-12T15:01:22.711910Z

hmm, I always thought of stubbing - returns a default value/does nothing; mocking - responds to complicated interactions, remembers how many times it was called with what arguments, etc

dm3 2017-07-12T15:02:29.759556Z

the “testable stateful thing” is more like an in-memory db instead of a file-based one

onetom 2017-07-12T15:02:50.774980Z

well, you heard about mock objects but not stub objects right?

dm3 2017-07-12T15:03:31.804788Z

does my definition disagree with what stub objects are? Wiki says > A Stub Object is one whose methods are stubs (or “mocks”?); that is, they do no useful work but prevent #doesNotUnderstand errors and return plausible values so that the computation can continue.

dm3 2017-07-12T15:03:57.822451Z

haha >(or “mocks”?)

onetom 2017-07-12T15:04:16.836279Z

URL?

onetom 2017-07-12T15:04:37.850621Z

because currently it's just you who are saying it 😜

onetom 2017-07-12T15:05:34.889904Z

It's like as if I'm saying: Wiki says > A Stub Object is something which @dm3 should use more often ;D

dm3 2017-07-12T15:05:41.894967Z

I think this is the best explanation: https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs. The one I quoted is from c2 wiki

onetom 2017-07-12T15:08:53.026023Z

yeah, thats what i consider the best source too and thats why i said earlier > we don't have a clear, consistent, shared vocabulary which the article expresses as: > The vocabulary for talking about this soon gets messy - all sorts of words are used: stub, mock, fake, dummy.

onetom 2017-07-12T15:09:31.051439Z

btw, that Gerard Meszaros he is referring to was also a Hungarian guy, like myself 😉

onetom 2017-07-12T15:10:10.077523Z

and mészáros means butcher 😉

dm3 2017-07-12T15:10:27.089138Z

ok 🙂

dm3 2017-07-12T15:12:34.174170Z

Did any of your questions get answered though?

onetom 2017-07-12T15:12:48.182982Z

so maybe we should agree then on not using any of these dummy, fake, stub, spy, mock terms, just test double instead

onetom 2017-07-12T15:13:26.208399Z

since we are not even talking about object oriented code strictly

onetom 2017-07-12T15:17:00.353849Z

our stateful constructs though are still objects in "reality" and they should be differentiated from the functions working with them. there are two distinct ways how functions can use such objects: • explicitly depending on them, meaning they receive them as parameters • implicitly depending on them, meaning they have a reference wired into them at compile time

onetom 2017-07-12T15:18:19.407028Z

and no, i don't think we got any clearer, since i asked 3 quite specific questions and i didn't get 3 corresponding specific answers

onetom 2017-07-12T15:20:47.507128Z

regarding with-redefs im on the same view as expressed here: https://www.reddit.com/r/Clojure/comments/4wrjw5/withredefs_doesnt_play_well_with_coreasync/ > So IMO, the solution is simple. Don't use with-redefs. Any time you see "with-redefs" think "nasty hack" in your mind.

dm3 2017-07-12T15:22:38.582620Z

well, my answers are: Q1 - 1st approach for integration tests, mocking API functions for unit tests Q2/Q3 - haven’t seen anyone use states as functions so no comments

dm3 2017-07-12T15:23:15.607354Z

and I like my redefs 🙂

dm3 2017-07-12T15:24:04.640585Z

I also don’t use core.async

onetom 2017-07-12T15:26:43.749844Z

the master of mount just gave an example of using functions as states: https://clojurians.slack.com/archives/C0H7M5HFE/p1499782555817759

onetom 2017-07-12T15:28:04.804164Z

and his article he referenced also gives such an example in form of the #'app.sms/send-sms state https://clojurians.slack.com/archives/C0H7M5HFE/p1499782582834684

dm3 2017-07-12T15:28:19.815107Z

oh, right 🙂 well, I haven’t had the need to do that

dm3 2017-07-12T15:28:49.834867Z

so I won’t be able to help

onetom 2017-07-12T15:29:18.854892Z

that's fine, but you can't say anymore that you haven't seen anyone using that approach 😉

dm3 2017-07-12T15:29:45.873288Z

yep

tolitius 2017-07-12T17:09:09.557088Z

@onetom: If I understood your questions correctly I believe the confusion is around the actual implementation vs. testing.

tolitius 2017-07-12T17:09:14.560122Z

In case states extend protocols, their stubs can be reified in start-with to a new behavior that would be useful for testing

tolitius 2017-07-12T17:09:19.563228Z

In case states are functions they can be simply replaced with testing functions in start-with. EmailSender is such an example. I would have a state to be a function that is able to send emails (similar to an "sms sender" example).

tolitius 2017-07-12T17:09:25.566509Z

In case states are IO, resources, etc.. objects (such as a database "conn"ection) and you'd like to test the code that uses them, I would only access them via functions that take them as arguments. For example, I would not do this:

tolitius 2017-07-12T17:09:31.569510Z

(defn query [query params]
  (jdbc/fetch conn query params))

tolitius 2017-07-12T17:09:35.571804Z

but would rather do this:

tolitius 2017-07-12T17:09:40.574617Z

(defn query [conn query params]
  (jdbc/fetch conn query params))

tolitius 2017-07-12T17:09:45.577533Z

in which case you can either stub or mock a conn state. i.e. usually an in memory db or you could mock conn object as well of course.

tolitius 2017-07-12T17:10:09.590843Z

> which approach do you promote and why?

tolitius 2017-07-12T17:10:14.593510Z

I don't like to promote an approach 🙂 I believe there is no one approach that works for all. I would personally avoid creating states for higher order constructs such as a query function above, and would only have a handful of states that are as low level as possible: sockets, db connections, thread pools, etc..

tolitius 2017-07-12T17:10:19.596737Z

from your example above I would keep notify, query, send and other functions as just functions and would only have two states: email-sender and conn. Functions that rely on states would take them as arguments.

onetom 2017-07-12T17:47:45.909088Z

@tolitius thanks!

onetom 2017-07-12T17:50:37.010786Z

so as a consequence, what would be the signature of the notify function? 1. (notify [db emailer addr msg]) 2. (notify [emailer db addr msg]) 3. (notify [[db emailer] addr msg]) 4. (notify [{:keys [db emailer]} addr msg])

tolitius 2017-07-12T18:15:29.899276Z

would really depend on a way you'd prefer to structure your application / functions. from your example above it seems notify does two things: * find an email address in a database by a user id * sends a message to the email address so something like:

(-&gt; (select conn query)
    (send-email msg))
it could either be used as ^^ in case your local scope where you call it from has access to conn and send-email, or if you call it from multiple places it could look something like this:
(defn find-email [conn id]
  (jdbc/select conn ... 'id' ))

(defn notify [send-email conn id msg]
  (-&gt; (find-email conn id)
      (send-email msg)))

1