Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to assert the contents of an array, indifferent of the ordering

I have a minitest spec:

it "fetches a list of all databases" do   get "/v1/databases"   json = JSON.parse(response.body)   json.length.must_equal           Database.count   json.map{|d| d["id"]}.must_equal Database.all.pluck(:id) end 

This, however, fails:

Expected: [610897332, 251689721]   Actual: [251689721, 610897332] 

I could order them both, but that adds clutter:

json.map{|d| d["id"]}.sort.must_equal Database.all.pluck(:id).sort 

As it is, the map{} is already somewhat irrelevant to the test and adding clutter, I'd prefer to not add even more.

Is there an assertion or helper to test if all items in enumerator1 are all in enumerator2?

like image 639
berkes Avatar asked Dec 02 '13 13:12

berkes


2 Answers

TL;DR The most direct way to check this is to sort the arrays before checking their equality.

json.map{|d| d["id"]}.sort.must_equal Database.all.pluck(:id).sort 

Still here? Okay. Let's talk about comparing elements in an array.

As it is, the map{} is already somewhat irrelevant to the test and adding clutter, I'd prefer to not add even more.

Well, that is part of the problem. Your JSON contains an array of JSON objects, while calling Database.pluck will return something else, presumably integers. You need to convert your JSON objects and your query to be the same datatype. So it's not accurate to say that the .map{} is irrelevant, and if it feels like clutter then that is because you are doing so many things in your assertion. Try splitting that line of code apart and using intention revealing names:

sorted_json_ids = json.map{|d| d["id"]}.sort sorted_db_ids   = Database.order(:id).pluck(:id) sorted_json_ids.must_equal sorted_db_ids 

It is more lines of code in your test, but better communicates the intent. And yet I hear your words "irrelevant" and "clutter" echoing in my mind. I bet you don't like this solution. "Its too much work!" And "Why do I have to be responsible for this?" Okay, okay. We have more options. How about a smarter assertion?

RSpec has a nice little matcher named match_array that does pretty much what you are looking for. It sorts and compares arrays and prints a nice message if they don't match. We could do something similar.

def assert_matched_arrays expected, actual   assert_equal expected.to_ary.sort, actual.to_ary.sort end  it "fetches a list of all databases" do   get "/v1/databases"   json = JSON.parse(response.body)   assert_matched_arrays Database.pluck(:id), json.map{|d| d["id"]} end 

"But that's an assertion and not an expectation!" Yeah, I know. Relax. You can turn an assertion into an expectation by calling infect_an_assertion. But to do this right, you probably want to add the assertion method so it can be used in every Minitest test. So in my test_helper.rb file I'd add the following:

module MiniTest::Assertions   ##   # Fails unless <tt>exp</tt> and <tt>act</tt> are both arrays and   # contain the same elements.   #   #     assert_matched_arrays [3,2,1], [1,2,3]    def assert_matched_arrays exp, act     exp_ary = exp.to_ary     assert_kind_of Array, exp_ary     act_ary = act.to_ary     assert_kind_of Array, act_ary     assert_equal exp_ary.sort, act_ary.sort   end end  module MiniTest::Expectations   ##   # See MiniTest::Assertions#assert_matched_arrays   #   #     [1,2,3].must_match_array [3,2,1]   #   # :method: must_match_array    infect_an_assertion :assert_matched_arrays, :must_match_array end 

Now your assertion can be used in any test, and your expectation will be available on every object.

it "fetches a list of all databases" do   get "/v1/databases"   json = JSON.parse(response.body)   json.map{|d| d["id"]}.must_match_array Database.pluck(:id) end 
like image 138
blowmage Avatar answered Oct 15 '22 00:10

blowmage


MiniTest Rails Shoulda has an assert_same_elements assertion, which:

Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered.

assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes 
like image 44
James Martin Avatar answered Oct 15 '22 01:10

James Martin