Checking values exist in maps

64 views
Skip to first unread message

cook....@gmail.com

unread,
May 8, 2015, 6:25:30 AM5/8/15
to mi...@googlegroups.com
I often want to check that a map contains all the correct values, but for some keys I just want to check that the value exists.  A regular example of this is timestamps - I might want something like this:

(f) => {:order {:payment {:timestamp <not nil>
                          :amount 123}
                :id      456}}

I don't know in my test what the timestamp value will be, but I don't seem to be able to use things like truthy or pos? when the value is nested in a map.   The docs suggest that using contains or just allow nested checkers to work, but I haven't seen this - the results always fail and show that midje tried to compare the truthy function to the actual value, rather than evaluating it.

How should I do this?

cook....@gmail.com

unread,
May 8, 2015, 6:33:25 AM5/8/15
to mi...@googlegroups.com
I usually end up doing something unreadable like 

(f) => (contains {:order {:payment (contains {:amount 123})
                          :id      456})
(-> (f) :order :payment :timestamp) => truthy

Brian Marick

unread,
May 8, 2015, 11:33:02 AM5/8/15
to mi...@googlegroups.com
I would probably use this:

user=> (fact
#_=> (get-in (f) [:order :payment :timestamp]) =not=> nil)


`just` and `contains` do allow checkers, but only at the top level of a collection. That is, the following works:

user=> (fact [1 2 3 4] => (contains [even? odd? even?]))
true

... but functions more deeply nested are compared with `=`, rather than be used as checkers.


As I'm thinking about Midje 2.0, I'm toying with the idea of letting `just` and `contains` trigger Midje's "extended equality" for entire subtrees. In that case, you'd write:


(f) => (contains {:order {:payment {:timestamp anything}}}}

Not 100% sure that's an improvement over the `get-in` version.

cook....@gmail.com

unread,
May 10, 2015, 11:56:57 AM5/10/15
to mi...@googlegroups.com
That would be much better, because I regularly need to check more than just one value.  I'd like to say:

(f) => (contains {:order {:payment {:timestamp anything :amount 100}}})

I think my use case arises because I write a lot of integration-level tests, so I'm checking real outputs from a running service.  

Incidentally, it would be even better if I didn't have to use contains - just being able to put "anything" in my expected value at any level would be ideal.

Thanks!

ChrisCruft

unread,
May 10, 2015, 9:26:39 PM5/10/15
to mi...@googlegroups.com
```
(just {:order (contains {:payment (contains {:timestamp truthy})})})
```
The trick is to next the just/contains so that extended equality is always in play.

Brian Marick

unread,
May 11, 2015, 10:45:03 AM5/11/15
to mi...@googlegroups.com


cook....@gmail.com wrote:
> Incidentally, it would be even better if I didn't have to use contains -
> just being able to put "anything" in my expected value at any level
> would be ideal.

I think it's a step too far to treat the right-hand side of an arrow as
meaning "contains":

(fact [1 2 3 4] => [1 2 3]) ; passes?

I think you'd want to write this:

(fact [1 2 3 4] => (contains [1 2 3]))

That leaves the question of what this should mean:

(fact (f) => [1 even? 3])

Should it match [1 2 3]? Or specifically a collection with a function:
[1 even? 3]?

I think the evidence is that people will typically expect [1 2 3] to
pass the test. For example, I had to change prerequisite arglist
matching to work that way because people (including me) kept expecting this:

(fact
(f) => 3
(provided
(g even?) => 1))

... to have `(g even?)` match `(g 2)`.

This is a potentially breaking change, so it would have to go in Midje
2.0. However, I doubt it would actually break any test I've ever written
for production code.

On the other hand, the `just` checker does support `:in-any-order` and
`:gaps-ok` qualifiers. It's a bit jarring that `just` is required for
those cases but not for the more common case.

(fact [1 2 3] => [1 even? odd?]
(fact [1 2 3] => (just [1 even? odd?] :in-any-order)

I have used `:in-any-order` a fair amount. However: `just` with no
qualifiers could just be a more verbose way of writing the default.

Upshot: I think this should work as you expect:

> (f) => (contains {:order {:payment {:timestamp anything :amount 100}}})

... and this should probably also work to mean an exact match:

>> (f) => {:order {:payment {:timestamp anything :amount 100}})

cook....@gmail.com

unread,
May 15, 2015, 11:19:09 AM5/15/15
to mi...@googlegroups.com
Yeah, exactly that.  If I want an exact match with a nested key that must exist but can take any value, I want to say this:

(f) => {:outer {:inner  anything
                :inner2 "exactly this"}}

If I just want to check that a bunch of nested keys are present but not exclusively, and one of them can take any value:

(f) => (contains {:outer {:inner  anything
                          :inner2 "exactly this"}}

Basically I just want things like anything and truthy always to work because they're really handy.

Brian Marick

unread,
May 17, 2015, 2:07:13 PM5/17/15
to mi...@googlegroups.com


cook....@gmail.com wrote:
> Basically I just want things like anything and truthy always to work
> because they're really handy.

In Midje 2, that'll work within all of the collection checkers.
Reply all
Reply to author
Forward
0 new messages