Skip to content

Commit

Permalink
Merge pull request #216 from dblock/rspec-matches
Browse files Browse the repository at this point in the history
Added start_typing RSpec matcher and extended existing matchers to work without arguments
  • Loading branch information
dblock committed Mar 25, 2019
2 parents bc25274 + e926d31 commit 1f54735
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2018-08-27 14:16:34 +0200 using RuboCop version 0.58.2.
# on 2019-03-25 10:27:43 -0400 using RuboCop version 0.58.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
### 0.12.1 (Next)

* Your contribution here.
* [#209](https://github.com/slack-ruby/slack-ruby-bot/pull/209): Allow `respond_to_slack_message` and `respond_to_slack_messages` without arguments - [@dblock](https://github.com/dblock).
* [#216](https://github.com/slack-ruby/slack-ruby-bot/pull/216): Added `start_typing` RSpec matcher - [@dblock](https://github.com/dblock).
* [#214](https://github.com/slack-ruby/slack-ruby-bot/pull/214): Add passenger deployment documentation - [@cybercrediators](https://github.com/cybercrediators).
* Your contribution here.

### 0.12.0 (2019/2/25)

Expand Down
75 changes: 72 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ Get help.

Hooks are event handlers and respond to Slack RTM API [events](https://api.slack.com/events), such as [hello](lib/slack-ruby-bot/hooks/hello.rb) or [message](lib/slack-ruby-bot/hooks/message.rb). You can implement your own in a couple of ways:

#### Implement and register a Hook Handler
#### Implementing and registering a Hook Handler

A Hook Handler is any object that respond to a `call` message, like a proc, instance of an object, class with a `call` class method, etc.

Expand Down Expand Up @@ -653,14 +653,17 @@ Again, the View will have access to the most up to date `client`, `data`, and `m

View methods are not matched to routes, so there is no restriction on how to name methods as there is in Controllers.

### RSpec Shared Behaviors
### Testing

Slack-ruby-bot ships with a number of shared RSpec behaviors that can be used in your RSpec tests.
#### RSpec Shared Behaviors

Slack-ruby-bot comes with a number of shared RSpec behaviors that can be used in your RSpec tests.

* [behaves like a slack bot](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/it_behaves_like_a_slack_bot.rb): A bot quacks like a Slack Ruby bot.
* [respond with slack message](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_message.rb): The bot responds with a message.
* [respond with slack messages](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_messages.rb): The bot responds with a multiple messages.
* [respond with error](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_error.rb): An exception is raised inside a bot command.
* [start typing](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/start_typing.rb): The bot calls `client.start_typing`.

Require `slack-ruby-bot/rspec` in your `spec_helper.rb` along with the following dependencies in Gemfile.

Expand All @@ -673,6 +676,72 @@ group :development, :test do
end
```

Use the `respond_with_slack_message` matcher.

```ruby
describe SlackRubyBot::Commands do
it 'responds with any message' do
expect(message: "#{SlackRubyBot.config.user} hi").to respond_with_slack_message
end
it 'says hi' do
expect(message: "#{SlackRubyBot.config.user} hi").to respond_with_slack_message('hi')
end
end
```

Use the `respond_with_slack_messages` matcher for multiple messages.

```ruby
describe SlackRubyBot::Commands do
it 'responds with more than one message' do
expect(message: "#{SlackRubyBot.config.user} count").to respond_with_slack_messages
end
it 'says one and two' do
expect(message: "#{SlackRubyBot.config.user} count").to respond_with_slack_messages(['one', 'two'])
end
end
```

Message matchers support regular expressions.

```ruby
describe SlackRubyBot::Commands do
it 'says hi' do
expect(message: "#{SlackRubyBot.config.user} hi").to respond_with_slack_message(/hi/)
end
end
```

Check that the bot called `client.start_typing(channel: 'channel')`.

```ruby
describe SlackRubyBot::Commands do
it 'starts typing on channel' do
expect(message: "#{SlackRubyBot.config.user} hi").to start_typing(channel: 'channel')
end
end
```

#### Testing Lower Level Messages

You can test client behavior at a lower level by fetching the message hook. The following example expects a bot command to call `client.typing(channel: data.channel)`.

```ruby
describe SlackRubyBot::Commands do
let(:app) { Server.new }
let(:client) { app.send(:client) }
let(:message_hook) { SlackRubyBot::Hooks::Message.new }
it 'receives a typing event' do
expect(client).to receive(:typing)
message_hook.call(
client,
Hashie::Mash.new(text: "#{SlackRubyBot.config.user} type something", channel: 'channel')
)
end
end
end
```

### Useful Libraries

* [newrelic-slack-ruby-bot](https://github.com/dblock/newrelic-slack-ruby-bot): NewRelic instrumentation for slack-ruby-bot.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

RSpec::Matchers.define :not_respond do
match do |actual|
client = if respond_to?(:client)
send(:client)
else
SlackRubyBot::Client.new
end
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new

message_command = SlackRubyBot::Hooks::Message.new
channel, user, message, attachments = parse(actual)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

RSpec::Matchers.define :respond_with_error do |error, error_message|
match do |actual|
client = if respond_to?(:client)
send(:client)
else
SlackRubyBot::Client.new
end
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new

message_command = SlackRubyBot::Hooks::Message.new
channel, user, message, attachments = parse(actual)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
require 'rspec/expectations'

RSpec::Matchers.define :respond_with_slack_message do |expected|
include SlackRubyBot::SpecHelpers

match do |actual|
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
def client.test_messages
@test_received_messages
end

def client.say(options = {})
super
@test_received_messages = @test_received_messages.nil? ? [] : @test_received_messages
@test_received_messages.push options
end

message_command = SlackRubyBot::Hooks::Message.new
channel, user, message, attachments = parse(actual)

allow(Giphy).to receive(:random) if defined?(Giphy)

allow(client).to receive(:message)
allow(client).to receive(:message) do |options|
@messages ||= []
@messages.push options
end

message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
@messages = client.test_messages
expect(client).to have_received(:message).with(hash_including(channel: channel, text: expected)).once

matcher = have_received(:message).once
matcher = matcher.with(hash_including(channel: channel, text: expected)) if channel && expected

expect(client).to matcher

true
end

failure_message do |_actual|
message = "expected to receive message with text: #{expected} once,\n received:"
message += @messages.count.zero? ? 'No response messages received' : @messages.inspect
message += @messages && @messages.any? ? @messages.inspect : 'none'
message
end
end
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
require 'rspec/expectations'

RSpec::Matchers.define :respond_with_slack_messages do |expected|
include SlackRubyBot::SpecHelpers

match do |actual|
raise ArgumentError, 'respond_with_slack_messages expects an array of ordered responses' unless expected.respond_to? :each
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
def client.test_messages
@test_received_messages
end
raise ArgumentError, 'respond_with_slack_messages expects an array of ordered responses' if expected && !expected.respond_to?(:each)

def client.say(options = {})
super
@test_received_messages = @test_received_messages.nil? ? [] : @test_received_messages
@test_received_messages.push options
end
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new

message_command = SlackRubyBot::Hooks::Message.new
channel, user, message, attachments = parse(actual)

allow(Giphy).to receive(:random) if defined?(Giphy)

allow(client).to receive(:message)
@messages ||= []
allow(client).to receive(:message) do |options|
@messages.push options
end

message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
@messages = client.test_messages

@responses = []
expected.each do |exp|
@responses.push(expect(client).to(have_received(:message).with(hash_including(channel: channel, text: exp)).once))

if expected && expected.any?
expected.each do |exp|
@responses.push(expect(client).to(have_received(:message).with(hash_including(channel: channel, text: exp)).once))
end
else
expect(@messages.size).to be > 1
end

true
end

failure_message do |_actual|
message = ''
expected.each do |exp|
message += "Expected text: #{exp}, got #{@messages[expected.index(exp)] || 'No Response'}\n" unless @responses[expected.index(exp)]
if expected && expected.any?
expected.map do |exp|
"Expected text: #{exp}, got #{@messages[expected.index(exp)] || 'none'}" unless @responses[expected.index(exp)]
end.compact.join("\n")
else
"Expected to receive multiple messages, got #{@messages.any? ? @messages.size : 'none'}"
end
message
end
end
30 changes: 30 additions & 0 deletions lib/slack-ruby-bot/rspec/support/slack-ruby-bot/start_typing.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'rspec/expectations'

RSpec::Matchers.define :start_typing do |expected|
include SlackRubyBot::SpecHelpers

match do |actual|
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new

message_command = SlackRubyBot::Hooks::Message.new

allow(client).to receive(:typing) do |options|
@test_options = options
end

channel, user, message, attachments = parse(actual)
message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))

matcher = have_received(:typing).once
matcher = matcher.with(expected) if expected && expected.any?
expect(client).to matcher

true
end

failure_message do |_actual|
message = "expected to receive typing with: #{expected} once,\n received:"
message += @test_options && @test_options.any? ? @test_options.inspect : ' none'
message
end
end
18 changes: 18 additions & 0 deletions spec/slack-ruby-bot/rspec/respond_with_slack_message_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
describe RSpec do
let! :command do
Class.new(SlackRubyBot::Commands::Base) do
command 'no message' do |client, data, _match|
end
command 'single message with as_user' do |client, data, _match|
client.say(text: 'single response', channel: data.channel, as_user: true)
end
Expand All @@ -25,6 +27,22 @@ def app
expect(message: "#{SlackRubyBot.config.user} single message")
.to respond_with_slack_message(/single response/i)
end
it 'respond with any message' do
expect(message: "#{SlackRubyBot.config.user} single message")
.to respond_with_slack_message
end
it 'correctly reports error' do
expect do
expect(message: "#{SlackRubyBot.config.user} single message")
.to respond_with_slack_message('another response')
end.to raise_error RSpec::Expectations::ExpectationNotMetError, "expected to receive message with text: another response once,\n received:[{:text=>\"single response\", :channel=>\"channel\"}]"
end
it 'correctly reports error without message' do
expect do
expect(message: "#{SlackRubyBot.config.user} no message")
.to respond_with_slack_message('another response')
end.to raise_error RSpec::Expectations::ExpectationNotMetError, "expected to receive message with text: another response once,\n received:none"
end
it 'respond_with_single_message_using_partial_regex_match' do
expect(message: "#{SlackRubyBot.config.user} single message")
.to respond_with_slack_message(/si[n|N]gle/)
Expand Down
34 changes: 34 additions & 0 deletions spec/slack-ruby-bot/rspec/respond_with_slack_messages_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
describe RSpec do
let! :command do
Class.new(SlackRubyBot::Commands::Base) do
command 'do not respond' do |client, data, _match|
end
command 'respond once' do |client, data, _match|
client.say(text: 'response', channel: data.channel, as_user: true)
end
command 'respond with as_user' do |client, data, _match|
5.times do |i|
client.say(text: "response #{i}", channel: data.channel, as_user: true)
Expand Down Expand Up @@ -29,6 +34,26 @@ def app
expect(message: "#{SlackRubyBot.config.user} respond 5 times")
.to respond_with_slack_messages(expected_responses)
end
it 'respond with multiple messages' do
expect(message: "#{SlackRubyBot.config.user} respond 5 times")
.to respond_with_slack_messages
end
it 'respond once' do
expect do
expect(message: "#{SlackRubyBot.config.user} respond once")
.to respond_with_slack_messages
end.to raise_error RSpec::Expectations::ExpectationNotMetError, 'Expected to receive multiple messages, got 1'
end
it 'do not respond' do
expect(message: "#{SlackRubyBot.config.user} do not respond")
.to_not respond_with_slack_messages
end
it 'correctly reports error on do not respond' do
expect do
expect(message: "#{SlackRubyBot.config.user} do not respond")
.to respond_with_slack_messages
end.to raise_error RSpec::Expectations::ExpectationNotMetError, 'Expected to receive multiple messages, got none'
end
it 'respond_with_multiple_messages_using_string_matches' do
expected_responses = []
5.times { |i| expected_responses.push("response #{i}") }
Expand All @@ -41,6 +66,15 @@ def app
expect(message: "#{SlackRubyBot.config.user} respond 5 times")
.not_to respond_with_slack_messages(expected_responses)
end
it 'correctly reports error' do
expect do
expected_responses = []
6.times { |i| expected_responses.push("response #{i}") }
expect(message: "#{SlackRubyBot.config.user} respond 5 times")
.to respond_with_slack_messages(expected_responses)
end.to raise_error RSpec::Expectations::ExpectationNotMetError, 'Expected text: response 5, got none'
end

it 'respond_with_multiple_messages_using_string_matches_with_extra_arg' do
expected_responses = []
5.times { |i| expected_responses.push("response #{i}") }
Expand Down
Loading

0 comments on commit 1f54735

Please sign in to comment.