Testing Sunspot With Sunspot_Matchers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
My impression is that most people implement this method in the controller. We certainly did, and many samples I looked at did the same. This makes a certain sense, as search likely maps to a controller action. But having code of this size and complexity in a controller certainly smells fishy and makes me think we should extract this out.
We could extract this to the object being searched. But I lean towards extracting it to its own class, something whose sole responibility is encapsulating this Sunspot interaction.
And by extracting it to its own thing, we can give it its own suite of tests. But how do you test search? I don’t really want to ensure that Sunspot works as advertised. And I certainly don’t want to populate a database with a bunch of fixtures just so that I can search them. Boo for slow tests.
My inclination here would be to set expectations. If I execute a search for
name = blah then Sunspot should receive
with(:name, 'blah'). Simple enough.
The fact that Sunspot wraps all of its search setup in a block breaks my normal approach. There’s apparently nothing for me to set an expectation on. I tried a variety of increasingly dumb ways to test this, all to no avail.
Thankfully my boss is a better googler than I am and the people at Pivotal Labs are smarter than I am. My boss found Sunspot Matchers from a Pivotal Labs blog post. This gem gives you the ability to spy on what Sunspot is doing and make assertions about the search configuration. It’s mostly designed for Rspec, but a kind soul added Test::Unit support. But there are some slight differences between the two.
One of those differences is in spying on fulltext searching. In rspec,
:fulltext are synonymous. But the Test::Unit version only supports
:keywords. A minor thing, but I have submitted a pull request that fixes it. So it’s possible that the gem is fixed by the time you read this.
Also, the behavior can also be slightly odd if you have a situation like the following. Say my Sunspot code has these two lines:
The first line just searches on whatever
fee_states the user has selected, but only if the user has selected one or more fee_states. The 2nd line explicitly excludes Protected state if the user is prevented from seeing them.
In testing this, I would expect this test work:
1 2 3 4 5
Straightforward enough. I’m not including the
fee_states key in my search params, so there should be no search criteria for fee_states.
Instead, this fails. It fails because Sunspot still adds the exclusion of fees in the procteced state.
Now, I don’t think this should fail because I haven’t added any
with clauses for fee_states. Yes, I’ve added a
without clause, but that’s different. But sunspot_matchers (or Sunspot) thinks otherwise. So I have to test like so:
1 2 3 4 5 6
Now the user can see protected fees, thus the
without clause isn’t added, thus my tests pass. But to do so I have to stub some methods totally unrelated to the logic under test. I’m not sure if the gem has to behave in this way, but I have opened an issue. Maybe I can help fix this as well.
Little glitches aside, sunspot_matchers totally saved my butt this week. Without it I couldn’t have possibly tested this code. And witohut tests the very necessary refactoring I did would have been next to impossible.