Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/leshill/resque_spec

RSpec matcher for Resque
https://github.com/leshill/resque_spec

Last synced: about 2 months ago
JSON representation

RSpec matcher for Resque

Awesome Lists containing this project

README

        

ResqueSpec
==========

[![Build
Status](https://travis-ci.org/leshill/resque_spec.png)](https://travis-ci.org/leshill/resque_spec)

A test double of Resque for RSpec and Cucumber. The code was originally based
on
[http://github.com/justinweiss/resque\_unit](http://github.com/justinweiss/resque_unit).

ResqueSpec will also fire Resque hooks if you are using them. See below.

Install
-------

Update your Gemfile to include `resque_spec` only in the *test* group (Not
using `bundler`? Do the necessary thing for your app's gem management and use
`bundler`. `resque_spec` monkey patches `resque` it should only be used with
your tests!)

```ruby
group :test do
gem 'resque_spec'
end
```

Cucumber
--------

By default, the above will add the `ResqueSpec` module and make it available in
Cucumber. If you want the `with_resque` and `without_resque` helpers, manually
require the `resque_spec/cucumber` module:

```ruby
require 'resque_spec/cucumber'
```
This can be done in `features/support/env.rb` or in a specific support file
such as `features/support/resque.rb`.

What is ResqueSpec?
===================

ResqueSpec implements the *stable API* for Resque 1.19+ (which is `enqueue`,
`enqueue_to`, `dequeue`, `peek`, `reserve`, `size`, the Resque hooks, and
because of the way `resque_scheduler` works `Job.create` and `Job.destroy`).

It does not have a test double for Redis, so this may lead to some interesting and
puzzling behaviour if you use some of the popular Resque plugins (such as
`resque_lock`).

Resque with Specs
=================

Given this scenario

Given a person
When I recalculate
Then the person has calculate queued

And I write this spec using the `resque_spec` matcher

```ruby
describe "#recalculate" do
before do
ResqueSpec.reset!
end

it "adds person.calculate to the Person queue" do
person.recalculate
expect(Person).to have_queued(person.id, :calculate)
end
end
```

And I see that the `have_queued` assertion is asserting that the `Person` queue has a job with arguments `person.id` and `:calculate`

And I take note of the `before` block that is calling `reset!` for every spec

And I might use the `in` statement to specify the queue:
```ruby
describe "#recalculate" do
before do
ResqueSpec.reset!
end

it "adds person.calculate to the Person queue" do
person.recalculate
expect(Person).to have_queued(person.id, :calculate).in(:people)
end
end
```

And I might write this as a Cucumber step

```ruby
Then /the (\w?) has (\w?) queued/ do |thing, method|
thing_obj = instance_variable_get("@#{thing}")
expect(thing_obj.class).to have_queued(thing_obj.id, method.to_sym)
end
```

Then I write some code to make it pass:

```ruby
class Person
@queue = :people

def recalculate
Resque.enqueue(Person, id, :calculate)
end
end
```

You can check the size of the queue in your specs too.

```ruby
describe "#recalculate" do
before do
ResqueSpec.reset!
end

it "adds an entry to the Person queue" do
person.recalculate
expect(Person).to have_queue_size_of(1)
end
end
```

Turning off ResqueSpec and calling directly to Resque
-----------------------------------------------------

Occasionally, you want to run your specs directly against Resque instead of
ResqueSpec. For one at a time use, pass a block to the `without_resque_spec`
helper:

```ruby
describe "#recalculate" do
it "recalculates the persons score" do
without_resque_spec do
person.recalculate
end
... assert recalculation after job done
end
end
```

Or you can manage when ResqueSpec is disabled by flipping the
`ResqueSpec.disable_ext` flag:

```ruby
# disable ResqueSpec
ResqueSpec.disable_ext = true
```

You will most likely (but not always, see the Resque docs) need to ensure that
you have `redis` running.

ResqueMailer with Specs
=======================

To use with [ResqueMailer](https://github.com/zapnap/resque_mailer) you should
have an initializer that does *not* exclude the `test` (or `cucumber`)
environment. Your initializer will probably end up looking like:

```ruby
# config/initializers/resque_mailer.rb
Resque::Mailer.excluded_environments = []
```

If you have a mailer like this:

```ruby
class ExampleMailer < ActionMailer::Base
include Resque::Mailer

def welcome_email(user_id)
end
end
```

You can write a spec like this:

```ruby
describe "#welcome_email" do
before do
ResqueSpec.reset!
Examplemailer.welcome_email(user.id).deliver
end

subject { described_class }
it { should have_queue_size_of(1) }
it { should have_queued(:welcome_email, [user.id]) }
end
```

resque-scheduler with Specs
==========================

Update the Gemfile to enable the `resque-schedular` matchers:

```ruby
group :test do
gem 'resque_spec', require: 'resque_spec/scheduler'
end
```

Given this scenario

Given a person
When I schedule a recalculate
Then the person has calculate scheduled

And I write this spec using the `resque_spec` matcher

```ruby
describe "#recalculate" do
before do
ResqueSpec.reset!
end

it "adds person.calculate to the Person queue" do
person.recalculate
expect(Person).to have_scheduled(person.id, :calculate)
end
end
```

And I might use the `at` statement to specify the time:

```ruby
describe "#recalculate" do
before do
ResqueSpec.reset!
end

it "adds person.calculate to the Person queue" do
person.recalculate

# Is it scheduled to be executed at 2010-02-14 06:00:00 ?
expect(Person).to have_scheduled(person.id, :calculate).at(Time.mktime(2010,2,14,6,0,0))
end
end
```

And I might use the `in` statement to specify time interval (in seconds):

```ruby
describe "#recalculate" do
before do
ResqueSpec.reset!
end

it "adds person.calculate to the Person queue" do
person.recalculate

# Is it scheduled to be executed in 5 minutes?
expect(Person).to have_scheduled(person.id, :calculate).in(5 * 60)
end
end
```

You can also check the size of the schedule:

```ruby
describe "#recalculate" do
before do
ResqueSpec.reset!
end

it "adds person.calculate to the Person queue" do
person.recalculate

expect(Person).to have_schedule_size_of(1)
end
end
```

(And I take note of the `before` block that is calling `reset!` for every spec)

You can explicitly specify the queue when using enqueue_at_with_queue and
enqueue_in_with_queue:

```ruby
describe "#recalculate_in_future" do
before do
ResqueSpec.reset!
end

it "adds person.calculate to the :future queue" do
person.recalculate_in_future

Person.should have_schedule_size_of(1).queue(:future)
end
end
```

And I might write this as a Cucumber step

```ruby
Then /the (\w?) has (\w?) scheduled/ do |thing, method|
thing_obj = instance_variable_get("@#{thing}")
expect(thing_obj.class).to have_scheduled(thing_obj.id, method.to_sym)
end
```

Then I write some code to make it pass:

```ruby
class Person
@queue = :people

def recalculate
Resque.enqueue_at(Time.now + 3600, Person, id, :calculate)
end

def recalculate_in_future
Resque.enqueue_at_with_queue(:future, Time.now + 3600, Person, id, :calculate)
end
end
```

Performing Jobs in Specs
========================

Normally, ResqueSpec does not perform queued jobs within tests. You may want to
make assertions based on the result of your jobs. ResqueSpec can process jobs
immediately as they are queued or under your control.

Performing jobs immediately
---------------------------

To perform jobs immediately, you can pass a block to the `with_resque` helper:

Given this scenario

Given a game
When I score
Then the game has a score

I might write this as a Cucumber step

```ruby
When /I score/ do
with_resque do
visit game_path
click_link 'Score!'
end
end
```

Or I write this spec using the `with_resque` helper

```ruby
describe "#score!" do
before do
ResqueSpec.reset!
end

it "increases the score" do
with_resque do
game.score!
end
expect(game.score).to == 10
end
end
```

You can turn this behavior on by setting `ResqueSpec.inline = true`.

Performing jobs at your discretion
----------------------------------

You can perform the first job on a queue at a time, or perform all the jobs on
a queue. Use `ResqueSpec#perform_next(queue_name)` or
`ResqueSpec#perform_all(queue_name)`

Given this scenario:

Given a game
When I score
And the score queue runs
Then the game has a score

I might write this as a Cucumber step

```ruby
When /the (\w+) queue runs/ do |queue_name|
ResqueSpec.perform_all(queue_name)
end
```

Hooks
=====

Resque provides hooks at different points of the queueing lifecylce.
ResqueSpec fires these hooks when appropriate.

The before and after `enqueue` hooks are always called when you use
`Resque#enqueue`. If your `before_enqueue` hook returns `false`, the job will
not be queued and `after_enqueue` will not be called.

The `perform` hooks: before, around, after, and on failure are fired by
ResqueSpec if you are using the `with_resque` helper or set `ResqueSpec.inline = true`.

Important! If you are using resque-scheduler, `Resque#enqueue_at/enqueue_in`
does not fire the after enqueue hook (the job has not been queued yet!), but
will fire the `perform` hooks if you are using `inline` mode.

Note on Patches/Pull Requests
=============================

* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a
future version unintentionally.
* Commit, do not mess with rakefile, version, or history.
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
* Send me a pull request. Bonus points for topic branches.

Author
======

I made `resque_spec` because **resque** is awesome and should be easy to spec.
Follow me on [Github](https://github.com/leshill) and
[Twitter](https://twitter.com/leshill).

Contributors
============

* Kenneth Kalmer (@kennethkalmer) : rspec dependency fix
* Brian Cardarella (@bcardarella) : fix mutation bug
* Joshua Davey (@joshdavey) : with\_resque helper
* Lar Van Der Jagt (@supaspoida) : with\_resque helper
* Evan Sagge (@evansagge) : Hook in via Job.create, have\_queued.in
* Jon Larkowski (@l4rk) : inline perform
* James Conroy-Finn (@jcf) : spec fix
* Dennis Walters (@ess) : enqueue\_in support
* (@RipTheJacker) : remove\_delayed support
* Kurt Werle (@kwerle) : explicit require spec for v020
* (@dwilkie) : initial before\_enqueue support
* Marcin Balinski (@marcinb) : have\_schedule\_size\_of matcher, schedule matcher at, in
* (@alexeits) : fix matcher in bug with RSpec 2.8.0
* (@ToadJamb) : encode/decode of Resque job arguments
* Mateusz Konikowski (@mkonikowski) : support for anything matcher
* Mathieu Ravaux (@mathieuravaux) : without\_resque\_spec support
* Arjan van der Gaag (@avdgaag) : peek support
* (@dtsiknis) : Updated removed\_delayed
* Li Ellis Gallardo (@lellisga) : fix inline/disable\_ext bug
* Jeff Deville (@jeffdeville) : Resque.size
* Frank Wambutt (@opinel) : Fix DST problem in `have_scheduled`
* Luke Melia (@lukemelia) : Add `times` chained matcher
* Pablo Fernandez (@heelhook) : Add `have_queue_size_of_at_least` and `have_schedule_size_of_at_least` matchers
* (@k1w1) : Add support for enqueue\_at\_with\_queue/enqueue\_in\_with\_queue
* Ozéias Sant'ana (@ozeias) : Update specs to RSpec 2.10
* Yuya Kitajima (@yuyak) : Add ResqueMailer examples to README
* Andrés Bravo (@andresbravog) : Replace `rspec` dependency with explicit dependencies
* Ben Woosley (@Empact) : Loosen rubygems version constraint
* Jeff Dickey (@dickeyxxx) : Remove 2.0 warnings, added Travis
* Earle Clubb (@eclubb) : `be_queued` matcher
* Erkki Eilonen (@erkki) : RSpec 3 support
* Gavin Heavyside (@gavinheavyside) : RSpec three warnings
* Pavel Khrulev (@PaulSchuher) : Resque 2 and RSpec 3 support
* Ilya Katz (@ilyakatz) : Cleanup README.md for RSpec 3
* (@addbrick) : Compare times as integers in `have_scheduled` matcher
* Serious Haircut (@serioushaircut) : Fix ArgumentListMatcher to make it work with any\_args
* Harry Lascelles (@hlascelles) : Fix error when resque-spec is disabled

Copyright
=========

Copyright (c) 2010-2015 Les Hill. See LICENSE for details.