1
0
зеркало из https://github.com/glebtv/telegram-bot.git synced 2026-08-28 15:26:18 +03:00

Make integration & controller specs consistent

Этот коммит содержится в:
Max Melentiev
2018-05-21 12:19:44 +03:00
родитель eeb2569bd7
Коммит 673986ca7e
12 изменённых файлов: 154 добавлений и 120 удалений

Просмотреть файл

@@ -1,3 +1,11 @@
# Unreleased
- Make integration & controller specs consistent.
__Breaking changes__ for controller specs:
- Changed signature `dispatch(bot, update) => dispatch(update, bot)`.
- `update` helper is symbolized by default.
- `build_update(type, data)` is dropped in favor of `deep_stringify(type => data)`.
# 0.13.1
- Extracted typed response mappings to telegram-bot-types gem.

Просмотреть файл

@@ -416,47 +416,65 @@ RSpec.configure do |config|
end
```
There are integration and controller contexts for RSpec and some built-in matchers:
RSpec contexts and helpers are included automatically for groups and examples with matching
tags. In RSpec < 3.4 it's require to use `include_context` explicitly.
See [list of available helpers](https://github.com/telegram-bot-rb/telegram-bot/tree/master/lib/telegram/bot/rspec)
for details.
Integration tests simulate webhooks from Telegram. It works for Rails applications
using bot in webhooks mode without any additional configuration,
and may require some setup in other cases. It works on top of request-spec,
so it's required to use `type: :request` or place specs in `spec/requests` directory
with RSpec's `infer_spec_type_from_file_location!`.
```ruby
# spec/requests/telegram_webhooks_spec.rb
require 'telegram/bot/rspec/integration'
RSpec.describe TelegramWebhooksController, :telegram_bot do
# for old rspec add:
# for old RSpec:
# include_context 'telegram/bot/integration'
# Main method is #dispatch(update). Some helpers are:
# #dispatch_message(text, options = {})
# #dispatch_command(cmd, *args)
# Available matchers can be found in Telegram::Bot::RSpec::ClientMatchers.
it 'shows usage of basic matchers'
# The most basic one is #make_telegram_request(bot, endpoint, params_matcher)
expect { dispatch_command(:start) }.
to make_telegram_request(bot, :sendMessage, hash_including(text: 'msg text'))
# There are some shortcuts for ba
expect { send_message('Hi') }.to send_telegram_message(bot, /msg regexp/, some: :option)
end
describe '#start' do
subject { -> { dispatch_command :start } }
subject { -> { dispatch_command :start } }sic actions.
# Using built in matcher for `respond_to`:
it { should respond_with_message 'Hi there!' }
end
# There is context for callback queries with related matchers.
# There is context for callback queries with related matchers,
# use :callback_query tag to include it.
describe '#hey_callback_query', :callback_query do
let(:data) { "hey:#{name}" }
let(:name) { 'Joe' }
it { should answer_callback_query('Hey Joe') }
it { should edit_current_message :text, text: 'Done' }
end
end
# For controller specs use
require 'telegram/bot/updates_controller/rspec_helpers'
RSpec.describe TelegramWebhooksController, type: :telegram_bot_controller do
# for old rspec add:
# include_context 'telegram/bot/updates_controller'
end
# Matchers are available for custom specs:
include Telegram::Bot::RSpec::ClientMatchers
expect(&process_update).to send_telegram_message(bot, /msg regexp/, some: :option)
expect(&process_update).
to make_telegram_request(bot, :sendMessage, hash_including(text: 'msg text'))
```
Place integration tests inside `spec/requests`
when using RSpec's `infer_spec_type_from_file_location!`,
or just add `type: :request` to `describe`.
There is context for testing bot controller in the way similar to Rails controller tests.
It's supposed to be low-level alternative for request specs
```ruby
require 'telegram/bot/updates_controller/rspec_helpers'
RSpec.describe TelegramWebhooksController, type: :telegram_bot_controller do
# for old RSpec:
# include_context 'telegram/bot/updates_controller'
end
```
See sample app for more examples.

Просмотреть файл

@@ -0,0 +1,46 @@
require 'telegram/bot/rspec'
require 'telegram/bot/rspec/message_helpers'
# Shared helpers for testing callback query updates.
RSpec.shared_context 'telegram/bot/callback_query' do
include_context 'telegram/bot/integration'
include_context 'telegram/bot/message_helpers'
subject { -> { dispatch callback_query: payload } }
let(:payload) { {id: callback_query_id, from: from, message: message, data: data} }
let(:callback_query_id) { 11 }
let(:message_id) { 22 }
let(:message) { {message_id: message_id, chat: chat, text: 'message text'} }
let(:data) { raise '`let(:data) { "callback query data here" }` is required' }
# Matcher to check that origin message got edited.
def edit_current_message(type, options = {})
description = 'edit current message'
options = options.merge(
message_id: message[:message_id],
chat_id: chat_id,
)
Telegram::Bot::RSpec::ClientMatchers::MakeTelegramRequest.new(
bot, :"editMessage#{type.to_s.camelize}", description: description
).with(hash_including(options))
end
# Matcher to check that callback query is answered.
def answer_callback_query(text = Regexp.new(''), options = {})
description = "answer callback query with #{text.inspect}"
text = a_string_matching(text) if text.is_a?(Regexp)
options = options.merge(
callback_query_id: payload[:id],
text: text,
)
Telegram::Bot::RSpec::ClientMatchers::MakeTelegramRequest.new(
bot, :answerCallbackQuery, description: description
).with(hash_including(options))
end
end
RSpec.configure do |config|
if config.respond_to?(:include_context)
config.include_context 'telegram/bot/callback_query', :telegram_bot, :callback_query
end
end

Просмотреть файл

@@ -1,10 +1,11 @@
require 'telegram/bot/rspec/message_helpers'
require 'telegram/bot/rspec/callback_query_helpers'
RSpec.shared_context 'telegram/bot/integration' do
include Telegram::Bot::RSpec::ClientMatchers
include_context 'telegram/bot/message_helpers'
let(:bot) { Telegram.bot }
let(:default_message_options) { {from: from, chat: chat} }
let(:from) { {id: from_id} }
let(:from_id) { 123 }
let(:chat) { {id: chat_id} }
let(:chat_id) { 456 }
let(:controller_path) do
route_name = Telegram::Bot::RoutesHelper.route_name_for_bot(bot)
Rails.application.routes.url_helpers.public_send("#{route_name}_path")
@@ -18,8 +19,7 @@ RSpec.shared_context 'telegram/bot/integration' do
let(:clear_session?) { described_class.respond_to?(:session_store) }
before { described_class.session_store.try!(:clear) if clear_session? }
include Telegram::Bot::RSpec::ClientMatchers
# Process update.
def dispatch(update)
if ActionPack::VERSION::MAJOR >= 5
post(controller_path, params: update.to_json, headers: request_headers)
@@ -27,59 +27,10 @@ RSpec.shared_context 'telegram/bot/integration' do
post(controller_path, update.to_json, request_headers)
end
end
def dispatch_message(text, options = {})
dispatch message: default_message_options.merge(options).merge(text: text)
end
def dispatch_command(*args)
options = args.last.is_a?(Hash) ? args.pop : {}
dispatch_message("/#{args.join ' '}", options)
end
# Matcher to check response. Make sure to define `let(:chat_id)`.
def respond_with_message(expected = Regexp.new(''))
raise 'Define chat_id to use respond_with_message' unless defined?(chat_id)
send_telegram_message(bot, expected, chat_id: chat_id)
end
end
RSpec.shared_context 'telegram/bot/callback_query', callback_query: true do
include_context 'telegram/bot/integration'
subject { -> { dispatch callback_query: payload } }
let(:payload) { {id: 11, from: from, message: message, data: data} }
let(:message) { {message_id: 22, chat: chat, text: 'message text'} }
# Matcher to check that origin message got edited.
def edit_current_message(type, options = {})
description = 'edit current message'
options = options.merge(
message_id: message[:message_id],
chat_id: chat_id,
)
Telegram::Bot::RSpec::ClientMatchers::MakeTelegramRequest.new(
bot, :"editMessage#{type.to_s.camelize}", description: description
).with(hash_including(options))
end
# Matcher to check that callback query is answered.
def answer_callback_query(text = Regexp.new(''), options = {})
description = "answer callback query with #{text.inspect}"
text = a_string_matching(text) if text.is_a?(Regexp)
options = options.merge(
callback_query_id: payload[:id],
text: text,
)
Telegram::Bot::RSpec::ClientMatchers::MakeTelegramRequest.new(
bot, :answerCallbackQuery, description: description
).with(hash_including(options))
end
end
RSpec.configure do |config|
if config.respond_to?(:include_context)
config.include_context 'telegram/bot/integration', :telegram_bot
config.include_context 'telegram/bot/callback_query', :telegram_bot, :callback_query
end
end

26
lib/telegram/bot/rspec/message_helpers.rb Обычный файл
Просмотреть файл

@@ -0,0 +1,26 @@
# Shared helpers for testing message updates.
RSpec.shared_context 'telegram/bot/message_helpers' do
let(:default_message_options) { {from: from, chat: chat} }
let(:from) { {id: from_id} }
let(:from_id) { 123 }
let(:chat) { {id: chat_id} }
let(:chat_id) { 456 }
# Shortcut for dispatching messages with default params.
def dispatch_message(text, options = {})
dispatch message: default_message_options.merge(options).merge(text: text)
end
# Dispatch command message.
def dispatch_command(cmd, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
args.unshift("/#{cmd}")
dispatch_message(args.join(' '), options)
end
# Matcher to check response. Make sure to define `let(:chat_id)`.
def respond_with_message(expected = Regexp.new(''))
raise 'Define chat_id to use respond_with_message' unless defined?(chat_id)
send_telegram_message(bot, expected, chat_id: chat_id)
end
end

Просмотреть файл

@@ -1,37 +1,30 @@
require 'telegram/bot/updates_controller/testing'
require 'telegram/bot/rspec/message_helpers'
RSpec.shared_context 'telegram/bot/updates_controller' do
include Telegram::Bot::RSpec::ClientMatchers
include_context 'telegram/bot/message_helpers'
let(:controller_class) { described_class }
let(:controller) do
controller_class.new(bot, update).tap do |x|
controller_class.new(*controller_args).tap do |x|
x.extend Telegram::Bot::UpdatesController::Testing
end
end
let(:update) { build_update(payload_type, payload) }
let(:controller_args) { [bot, deep_stringify(update)] }
let(:update) { {payload_type => payload} }
let(:payload_type) { :some_type }
let(:payload) { double(:payload) }
let(:bot) { Telegram::Bot::ClientStub.new(bot_name) }
let(:bot_name) { 'bot' }
let(:session) { controller.send(:session) }
let(:from_id) { 123 }
let(:chat_id) { 456 }
let(:default_message_options) { {from: {id: from_id}, chat: {id: chat_id}} }
include Telegram::Bot::RSpec::ClientMatchers
def dispatch(bot = self.bot, update = self.update)
controller.dispatch_again(bot, update)
end
def dispatch_message(text, options = {})
update = build_update :message, default_message_options.merge(options).merge(text: text)
dispatch bot, update
end
def build_update(type, content)
deep_stringify type => content
# Process update.
def dispatch(update = self.update, bot = self.bot)
controller.dispatch_again(bot, deep_stringify(update))
end
# Same as `.as_json` but mocks-friendly.
def deep_stringify(input)
case input
when Array then input.map(&method(__callee__))
@@ -39,11 +32,6 @@ RSpec.shared_context 'telegram/bot/updates_controller' do
else input
end
end
# Matcher to check response. Make sure to define `let(:chat_id)`.
def respond_with_message(expected)
send_telegram_message(bot, expected, chat_id: chat_id)
end
end
RSpec.configure do |config|

Просмотреть файл

@@ -7,7 +7,6 @@ RSpec.describe Telegram::Bot::Middleware do
describe '#call' do
subject { instance.call(env) }
let(:env) { {'action_dispatch.request.request_parameters' => json_body} }
let(:update) { {'message' => {'id' => 1}} }
let(:env) do
Rack::MockRequest.env_for('/',

Просмотреть файл

@@ -27,7 +27,7 @@ RSpec.describe Telegram::Bot::UpdatesController::CallbackQueryContext do
describe '#dispatch' do
subject { -> { dispatch } }
let(:payload_type) { :callback_query }
let(:payload) { {'data' => data} }
let(:payload) { {data: data} }
let(:data) { text }
let(:text) { 'asd qwe zxc' }

Просмотреть файл

@@ -2,9 +2,7 @@ RSpec.describe Telegram::Bot::UpdatesController::Instrumentation do
include_context 'telegram/bot/updates_controller'
subject { -> { dispatch } }
let(:update) do
build_update :message, default_message_options.merge(text: '/start')
end
let(:update) { {message: default_message_options.merge(text: '/start')} }
let(:controller_class) do
Class.new(Telegram::Bot::UpdatesController) do
@@ -36,11 +34,11 @@ RSpec.describe Telegram::Bot::UpdatesController::Instrumentation do
action = action_name(:start_processing)
expect(events[action].size).to eq(1)
expect(events[action][0].last).to include(update: update)
expect(events[action][0].last).to include(update: deep_stringify(update))
action = action_name(:process_action)
expect(events[action].size).to eq(1)
expect(events[action][0].last).to include(update: update)
expect(events[action][0].last).to include(update: deep_stringify(update))
end
end

Просмотреть файл

@@ -4,8 +4,8 @@ RSpec.describe Telegram::Bot::UpdatesController do
let(:respond_type) { :photo }
let(:result) { double(:result) }
let(:payload_type) { :message }
let(:payload) { {message_id: double(:message_id)} }
let(:chat) { {'id' => double(:chat_id)} }
let(:payload) { {message_id: double(:message_id), chat: chat} }
let(:chat) { {id: double(:chat_id)} }
shared_examples 'missing chat' do
context 'when chat is missing' do
@@ -19,9 +19,8 @@ RSpec.describe Telegram::Bot::UpdatesController do
include_examples 'missing chat'
it 'sets chat_id & reply_to_message_id' do
expect(controller).to receive(:chat) { chat }
expect(bot).to receive("send_#{respond_type}").
with(params.merge(chat_id: chat['id'])) { result }
with(params.merge(chat_id: chat[:id])) { result }
should eq result
end
end
@@ -31,19 +30,18 @@ RSpec.describe Telegram::Bot::UpdatesController do
include_examples 'missing chat'
it 'sets chat_id & reply_to_message_id' do
expect(controller).to receive(:chat) { chat }
expect(bot).to receive("send_#{respond_type}").with(params.merge(
chat_id: chat['id'],
chat_id: chat[:id],
reply_to_message_id: payload[:message_id],
)) { result }
should eq result
end
context 'when update is not set' do
let(:update) { {chat: chat} }
let(:controller_args) { [bot, chat: deep_stringify(chat)] }
it 'sets chat_id' do
expect(bot).to receive("send_#{respond_type}").
with(params.merge(chat_id: chat['id'])) { result }
with(params.merge(chat_id: chat[:id])) { result }
should eq result
end
end
@@ -70,7 +68,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
it 'sets chat_id & message_id' do
expect(bot).to receive("edit_message_#{type}").with(params.merge(
message_id: payload[:message][:message_id],
chat_id: payload[:message][:chat]['id'],
chat_id: payload[:message][:chat][:id],
)) { result }
should eq result
end

Просмотреть файл

@@ -36,7 +36,7 @@ RSpec.describe Telegram::Bot::UpdatesController::Session do
end
def build_message(text, from)
{'message' => {'text' => text, 'from' => from.stringify_keys}}
deep_stringify(message: {text: text, from: from})
end
it 'stores session between requests' do

Просмотреть файл

@@ -278,11 +278,13 @@ RSpec.describe Telegram::Bot::UpdatesController do
instance_eval(&block)
context 'when re-initialized' do
let(:controller) do
described_class.new(double(:other_bot), build_update(:message,
initial_update = deep_stringify message: {
text: 'original message',
from: double(:original_from),
chat: double(:original_chat),
)).tap { |x| x.send(:initialize, bot, update) }
}
described_class.new(double(:other_bot), initial_update).
tap { |x| x.send(:initialize, *controller_args) }
end
instance_eval(&block)
end
@@ -300,7 +302,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
end
context 'when options hash is given' do
let(:update) { {from: from, chat: chat} }
let(:controller_args) { [bot, from: from, chat: chat] }
with_reinitialize do
its(:bot) { should eq bot }
its(:update) { should eq nil }