зеркало из
https://github.com/glebtv/telegram-bot.git
synced 2026-08-28 15:26:18 +03:00
Make integration & controller specs consistent
Этот коммит содержится в:
@@ -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.
|
||||
|
||||
60
README.md
60
README.md
@@ -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.
|
||||
|
||||
|
||||
46
lib/telegram/bot/rspec/callback_query_helpers.rb
Обычный файл
46
lib/telegram/bot/rspec/callback_query_helpers.rb
Обычный файл
@@ -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
Обычный файл
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 }
|
||||
|
||||
Ссылка в новой задаче
Block a user