Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minitest assertions and indexing controls. #396

Merged
merged 7 commits into from
Aug 6, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Chewy is an ODM and wrapper for [the official Elasticsearch client](https://gith
* [NewRelic integration] (#newrelic-integration)
* [Rake tasks] (#rake-tasks)
* [Rspec integration] (#rspec-integration)
* [Minitest integration] (#minitest-integration)
* [TODO a.k.a coming soon:] (#todo-aka-coming-soon)
* [Contributing] (#contributing)

Expand Down Expand Up @@ -1230,6 +1231,12 @@ rake chewy:update[-users,projects] # updates every index in application except s

Just add `require 'chewy/rspec'` to your spec_helper.rb and you will get additional features: See [update_index.rb](lib/chewy/rspec/update_index.rb) for more details.

### Minitest integration

Add `require 'chewy/minitest/helpers'` to your test_helper.rb, and then for tests which you'd like indexing enabled, `include Chewy::Minitest::Helpers`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require 'chewy/minitest' ?


### DatabaseCleaner

If you use `DatabaseCleaner` in your tests with [the `transaction` strategy](https://github.com/DatabaseCleaner/database_cleaner#how-to-use), you may run into the problem that `ActiveRecord`'s models are not indexed automatically on save despite the fact that you set the callbacks to do this with the `update_index` method. The issue arises because `chewy` indexes data on `after_commit` run as default, but all `after_commit` callbacks are not run with the `DatabaseCleaner`'s' `transaction` strategy. You can solve this issue by changing the `Chewy.use_after_commit_callbacks` option. Just add the following initializer in your Rails application:

```ruby
Expand Down
1 change: 1 addition & 0 deletions lib/chewy/minitest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require 'chewy/minitest/helpers'
80 changes: 80 additions & 0 deletions lib/chewy/minitest/helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
require_relative 'search_index_receiver'

module Chewy
module Minitest
module Helpers
extend ActiveSupport::Concern

# Assert that an index *changes* during a block.
# @param (Chewy::Type) index the index / type to watch, eg EntitiesIndex::Entity.
# @param (Symbol) strategy the Chewy strategy to use around the block. See Chewy docs.
# @param (boolean) assert the index changes
# @param (boolean) bypass_actual_index
# True to preempt the http call to Elastic, false otherwise.
# Should be set to true unless actually testing search functionality.
#
# @return (SearchIndexReceiver) for optional further assertions on the nature of the index changes.
def assert_indexes index, strategy: :atomic, bypass_actual_index: true, &test_actions
type = Chewy.derive_type index
receiver = SearchIndexReceiver.new filter: index

bulk_method = type.method :bulk
# Manually mocking #bulk because we need to properly capture `self`
bulk_mock = -> (*bulk_args) do
receiver.catch bulk_args, self

unless bypass_actual_index
bulk_method.call *bulk_args
end

{}
end

type.define_singleton_method :bulk, bulk_mock

Chewy.strategy(strategy) do
test_actions.call
end

type.define_singleton_method :bulk, bulk_method

assert_includes receiver.updated_indexes, index, "Expected #{index} to be updated but it wasn't"

receiver
end

# Run indexing for the database changes during the block provided.
# By default, indexing is run at the end of the block.
# @param (Symbol) strategy the Chewy index update strategy see Chewy docs.
def run_indexing strategy: :atomic
Chewy.strategy strategy do
yield
end
end

class_methods do
# Declare that all tests in this file require real indexing, always.
# In my completely unscientific experiments, this roughly doubled test runtime.
# Use with trepidation.
def index_everything!
setup do
Chewy.strategy :urgent
end

teardown do
Chewy.strategy.pop
end
end
end

included do
teardown do
# always destroy indexes between tests
# Prevent croll pollution of test cases due to indexing
Chewy.massacre
end
end

end
end
end
89 changes: 89 additions & 0 deletions lib/chewy/minitest/search_index_receiver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Test helper class to provide minitest hooks for Chewy::Index testing.
#
# @note Intended to be used in conjunction with a test helper which mocks over the #bulk
# method on a Chewy::Type class. (See SearchTestHelper)
#
# The class will capture the data from the *param on the Chewy::Type#bulk method and
# aggregate the data for test analysis. Optionally, a (Chewy::Type) filter parameter
# can be provided which will cause undesired indexes and deletes to be discarded.
class SearchIndexReceiver
# @param (Chewy::Type) filter
# @see SearchIndexCatcher for an explanation of the filter.
def initialize filter: nil
@mutations = {}
@filter = filter
end

# @param bulk_params the bulk_params that should be sent to the Chewy::Type#bulk method.
# @param (Chewy::Type) type the Index::Type executing this query.
def catch bulk_params, type
return if filter? type
Array.wrap(bulk_params).map {|y| y[:body] }.flatten.each do |update|
if body = update[:delete]
mutation_for(type).deletes << body[:_id]
elsif body = update[:index]
mutation_for(type).indexes << body
end
end
end

# @param index return only index requests to the specified Chewy::Type index.
# @return the index changes captured by the mock.
def indexes_for index = nil
if index
mutation_for(index).indexes
elsif @filter
mutation_for(@filter).indexes
else
@mutations.transform_values {|v| v.indexes}
end
end
alias_method :indexes, :indexes_for

# @param index return only delete requests to the specified Chewy::Type index.
# @return the index deletes captured by the mock.
def deletes_for index = nil
if index
mutation_for(index).deletes
elsif @filter
mutation_for(@filter).deletes
else
@mutations.transform_values {|v| v.deletes}
end
end
alias_method :deletes, :deletes_for

# Check to see if a given object has been indexed.
# @param (#id) obj the object to look for.
# @return bool if the object was indexed.
def indexed? obj
indexes.map {|i| i[:_id]}.compact.include? obj.id
end

# Check to see if a given object has been deleted.
# @param (#id) obj the object to look for.
# @return bool if the object was deleted.
def deleted? obj
deletes.map {|i| i[:_id]}.compact.include? obj.id
end

# @return a list of Chewy::Type indexes changed.
def updated_indexes
@mutations.keys
end

private
# Get the mutation object for a given type.
# @param (Chewy::Type) type the index type to fetch.
# @return (#indexes, #deletes) an object with a list of indexes and a list of deletes.
def mutation_for type
@mutations[type] ||= OpenStruct.new(indexes: [], deletes: [])
end

def filter? type
return false unless @filter
return ! type == @filter
end

end