Fork me on GitHub
#testing
<
2021-08-10
>
Noah Bogart17:08:07

is there any good method for mocking something like java.util.Date. ? I am creating a map with a :date entry and don’t want to have to worry about what it actually is when testing against the other contents

Noah Bogart17:08:30

i know i could write (map #(dissoc % :date)) in my test but that’s so cumbersome, especially when writing multiple tests

Noah Bogart17:08:35

i’d prefer to just write (= [{:date nil :text "Hello!"}] (-> (sut/new-game) (sut/add-message "Hello!") (:messages)))

Noah Bogart17:08:06

i just had the thought that a simple (defn new-date [] (java.util.Date.)) allows me to use with-redefs , but is that doesn’t seem generalizable. It will work for now so I’m not going to spend more time on this, but i’m still curious

vemv17:08:15

Date. is akin to a side-effect. Some call it a side-cause or "coeffect"

👍 2
Russell Mull17:08:28

if your code depends on the clock, then you should consider giving it a now parameter

vemv17:08:54

^ exactly, that way you seggregate the side-cause

Russell Mull17:08:02

lol, side-cause is a great name

Noah Bogart17:08:24

interesting. so how do i pass it in? i hope to allow the engine (for a game) to add messages from anywhere, which makes me think it’ll be cumbersome to have to call (java.util.Date.) every time I want to add a message

Noah Bogart17:08:42

once I’ve done it a couple times i start thinking “okay, how do I refactor to remove duplication?”

Russell Mull17:08:00

you could have a little message constructor that captures the clock

Russell Mull17:08:29

(defn messasge [m] {:msg m :time (java.util.Date)})

Russell Mull17:08:07

wow, I'm completely incapable of typing today

Noah Bogart17:08:28

so I would call (add-message game (message "Hello!")) in the engine instead of (add-message game "Hello!")? and in a test I can just pass in (sut/add-message game {:date nil :text "Hello!"})?

Russell Mull17:08:58

sure, that's reasonable

Noah Bogart17:08:32

seems like all i’ve done is move the (java.util.Date.) from within add-message to message. maybe I don’t understand enough, or maybe I haven’t done a good enough job of showing my code up front lol

Noah Bogart17:08:37

classic X/Y problem

Noah Bogart17:08:44

(defn add-message [game message]
  (update game :messages conj {:date (java.util.Date.)
                               :text message}))

Russell Mull17:08:57

what are you trying to do by putting a nil in for the date?

Noah Bogart17:08:29

allow me to test against the whole :messages vector without having to deliberately exclude the :date key in my equality checks

👍 2
pithyless19:08:11

Hey @UEENNMX0T - sorry for resurrecting a week-old thread, but I think this is some real insight that may have been overlooked. > allow me to test against the whole `:messages` vector without having to deliberately exclude the `:date` key in my equality checks Irrespective of whether you want to have an external clock passed to functions (so you can test and easily control behavior based on time); if what you want to do is check equality without dates; then that is code that should exist somewhere directly in the test codebase:

(is (= expected-msgs (elide-dates generated-msgs)))
How you implement eldie-dates is up to you; it could be as simple as a helper function that does dissoc on each message in the collection, or it could use some fancy testing matching library (ala https://github.com/nubank/matcher-combinators) or some fancy data wrangling library (ala https://github.com/noprompt/meander or https://github.com/redplanetlabs/specter) or some fancy query library (ala https://github.com/lilactown/autonormal)

Noah Bogart19:08:49

ha I wrote get-messages which does what your elide-dates does: (defn get-messages [game] (mapv #(dissoc % :date) (:messages game))) . Glad to know i’m not too far off

Jacob Emcken15:03:53

This is an alternative which actually mocks "creating a date", whether it is better or worse 🤷

Jacob Emcken15:03:49

(defn now []
  (java.util.Date.))
  
(def freezed-time #inst "2022-03-09T15:54:02.699-00:00")
  
(with-redefs [now (constantly freezed-time)]
  (= (now) #inst "2022-03-09T15:54:02.699-00:00")) => true

👍 1
Noah Bogart15:03:41

That’s a good one too!

Jacob Emcken16:03:47

If you ever start using the "newer" (immutable) dates in Java: https://github.com/dm3/clojure.java-time They have a built-in with-clock that can help you mock time in tests:

(with-clock (system-clock "UTC")
  (zoned-date-time 2015 10))
=> #<java.time.ZonedDateTime 2015-10-01T00:00Z[UTC]>

Russell Mull17:08:56

ahh, I see. In that case, moving it does have some value.

👍 2
Russell Mull17:08:32

Another possibility, which I kind of hesitate to mention: if two messages are logically equal even if the dates are different, then perhaps the date should be metadata.

Russell Mull17:08:10

but it sounds like that's not the case, instead it's just for the test assertion.

vemv18:08:30

defn add-message [game message {:keys [date] :or {:date (Date.)}}]
poor man's DI / Functional Architecture

Noah Bogart18:08:55

oh! that’s clever