1
0
зеркало из https://github.com/glebtv/telegram-bot.git synced 2026-09-03 17:55:52 +03:00
Этот коммит содержится в:
Max Melentiev
2016-11-07 20:07:55 +03:00
родитель ececfa660e
Коммит 194fc78202
9 изменённых файлов: 275 добавлений и 3 удалений

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

@@ -6,6 +6,7 @@
- API methods from 2016-10-03 update
- Fix typo in module name: CallbackQueyContext -> CallbackQueryContext.
- Take `chat` from `message` for callback queries
- RSpec matchers.
# 0.8.0

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

@@ -336,6 +336,23 @@ There are also some helpers for controller tests.
Check out `telegram/bot/updates_controller/rspec_helpers` and
`telegram/bot/updates_controller/testing`.
Built-in RSpec matchers will help you to write tests fast:
```ruby
include Telegram::Bot::RSpec::ClientMatchers # no need if you already use controller herlpers
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'))
# controller specs are even simplier:
describe '#start' do
subject { -> { dispatch_message '/start' } }
it { should respond_with_message(/Hello/) }
end
# See sample app for more examples.
```
### Deploying
Use `rake telegram:bot:set_webhook` to update webhook url for all configured bots.

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

@@ -34,6 +34,7 @@ module Telegram
autoload :DebugClient, 'telegram/bot/debug_client'
autoload :Initializers, 'telegram/bot/initializers'
autoload :Middleware, 'telegram/bot/middleware'
autoload :RSpec, 'telegram/bot/rspec'
autoload :UpdatesController, 'telegram/bot/updates_controller'
autoload :UpdatesPoller, 'telegram/bot/updates_poller'
end

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

@@ -43,7 +43,7 @@ module Telegram
@requests = Hash.new { |h, k| h[k] = [] }
end
def request(action, body)
def request(action, body = {})
requests[action.to_sym] << body
end
end

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

@@ -0,0 +1,7 @@
module Telegram
module Bot
module RSpec
autoload :ClientMatchers, 'telegram/bot/rspec/client_matchers'
end
end
end

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

@@ -0,0 +1,151 @@
module Telegram
module Bot
module RSpec
# Proxy that uses RSpec::Mocks::ArgListMatcher when it's available.
# Otherwise just performs `#==` match.
#
# Also allows to check argumets with custom block.
class ArgListMatcher
attr_reader :expected, :expected_proc
def initialize(*args, &block)
@expected_proc = block if block_given?
@expected =
if mocks_matcher?
::RSpec::Mocks::ArgumentListMatcher.new(*args)
else
args
end
end
def args_match?(*actual)
if expected_proc
expected_proc[*actual]
true
elsif mocks_matcher?
expected.args_match?(*actual)
else
expected == actual
end
end
def args
mocks_matcher? ? expected.args : expected
end
def mocks_matcher?
defined?(::RSpec::Mocks::ArgumentListMatcher)
end
def to_s
if mocks_matcher?
expected.expected_args.inspect
elsif expected_proc
'(proc matcher)'
else
expected.inspect
end
end
end
# Matchers to test requests to Telegram API.
#
# Complex matchers requires `rspec-mocks` to be installed.
module ClientMatchers
class MakeTelegramRequest < ::RSpec::Matchers::BuiltIn::BaseMatcher
EXPECTATION_TYPES = {
exactly: :==,
at_most: :>=,
at_least: :<=,
}.freeze
attr_reader :performed_requests, :description
def initialize(bot, action, description: nil)
@bot = bot
@action = action
@description = description || "make #{action} telegram request"
exactly(1)
end
def matches?(proc) # rubocop:disable AbcSize
raise ArgumentError, 'matcher only supports block expectations' unless proc.is_a?(Proc)
original_requests_count = bot.requests[action].count
proc.call
@performed_requests = bot.requests[action].drop(original_requests_count)
@matching_requests_count = performed_requests.count do |request|
!arg_list_matcher || arg_list_matcher.args_match?(request)
end
expectation_method = EXPECTATION_TYPES[expectation_type]
expected_number.public_send(expectation_method, matching_requests_count)
end
def with(*args, &block)
@arg_list_matcher = ArgListMatcher.new(*args, &block)
self
end
EXPECTATION_TYPES.each_key do |type|
define_method type do |count|
@expectation_type = type
@expected_number = Integer(count)
self
end
end
def times
self
end
def failure_message
"expected to #{base_message}"
end
def failure_message_when_negated
"expected not to #{base_message}"
end
def supports_block_expectations?
true
end
private
attr_reader :bot, :action, :expectation_type, :expected_number,
:arg_list_matcher, :matching_requests_count
def base_message
"make #{expectation_type.to_s.tr('_', ' ')} #{expected_number} " \
"#{bot.inspect}.#{action} requests,".tap do |msg|
msg << " with #{arg_list_matcher}," if arg_list_matcher
msg << " but made #{matching_requests_count}"
if performed_requests
actual_args = performed_requests.map(&:inspect).join(', ')
msg << ", and #{performed_requests.count} with #{actual_args}"
end
end
end
end
# Check that bot performed request to telegram API:
#
# expect { dispatch_message('Hi!') }.
# to make_telegram_request(bot, :sendMessage).
# with(text: 'Hello!', chat_id: chat_id)
def make_telegram_request(bot, action)
MakeTelegramRequest.new(bot, action)
end
# Helper for asserting message is sent. Note that options are checked
# with `hash_including`. For strict checks use #make_telegram_request.
def send_telegram_message(bot, text = nil, options = {})
text = a_string_matching(text) if text.is_a?(Regexp)
options = options.merge(text: text) if text
description = "send telegram message #{text.inspect}"
MakeTelegramRequest.new(bot, :sendMessage, description: description).
with(hash_including(options))
end
end
end
end
end

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

@@ -50,7 +50,7 @@ module Telegram
# ControllerClass.new(bot, from: telegram_user, chat: telegram_chat).
# process(:help, *args)
#
class UpdatesController < AbstractController::Base # rubocop:disable ClassLength
class UpdatesController < AbstractController::Base
abstract!
require 'telegram/bot/updates_controller/session'

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

@@ -14,11 +14,14 @@ RSpec.shared_context 'telegram/bot/updates_controller' do
let(:bot_name) { 'bot' }
let(:session) { controller.send(:session) }
include Telegram::Bot::RSpec::ClientMatchers
def dispatch(bot = self.bot, update = self.update)
controller.dispatch_again(bot, update)
end
def dispatch_message(text, options = {})
def dispatch_message(text, options = nil)
options ||= respond_to?(:default_message_options) ? default_message_options : {}
update = build_update :message, options.merge(text: text)
dispatch bot, update
end
@@ -34,4 +37,9 @@ 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

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

@@ -0,0 +1,87 @@
RSpec.describe Telegram::Bot::RSpec::ClientMatchers do
let(:bot) { Telegram::Bot::ClientStub.new('TestBot') }
let(:other_bot) { Telegram::Bot::ClientStub.new('OtherTestBot') }
include described_class
describe '#make_telegram_request' do
context 'without args' do
it 'works for matching' do
block = ->(*) { bot.send_message(text: 'test') }
expect(&block).to make_telegram_request(bot, :sendMessage)
expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage).exactly(3).times
expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage).at_least(2).times
expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage).at_most(4).times
end
it 'works for not-matching' do
block = ->(*) { bot.get_me }
expect {}.to_not make_telegram_request(bot, :getMe)
expect { other_bot.get_me }.to_not make_telegram_request(bot, :getMe)
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe)
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe).exactly(2).times
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe).exactly(4).times
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe).at_least(4).times
expect { 3.times(&block) }.to_not make_telegram_request(bot, :getMe).at_most(2).times
end
end
context 'with args' do
it 'works for exact matching' do
args = {text: 'test', parseMode: :Markdown}
block = ->(*) { bot.send_message(args) }
expect(&block).to make_telegram_request(bot, :sendMessage).with(args)
expect(&block).to_not make_telegram_request(bot, :sendMessage).with(args.except(:text))
expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage).
with(args).exactly(3).times
end
it 'works for block matchers' do
args = {text: 'test', parseMode: :Markdown}
block = ->(*) { bot.send_message(args) }
expect(&block).to make_telegram_request(bot, :sendMessage).
with { |actual| expect(actual).to eq(args) }
# It ignores block's result! Custom expectations must be used inside block.
expect(&block).to make_telegram_request(bot, :sendMessage).
with { |actual| actual == {} }
expect do
expect(&block).to_not make_telegram_request(bot, :sendMessage).
with { |actual| expect(actual).to eq({}) }
end.to raise_error RSpec::Expectations::ExpectationNotMetError
n = -1
expect { 3.times { |i| bot.send_message text: i } }.
to make_telegram_request(bot, :sendMessage).exactly(3).times.
with { |actual| expect(actual).to eq(text: n += 1) }
end
it 'works for RSpec::Mocks matchers' do
args = {text: 'test', parseMode: :Markdown}
block = ->(*) { bot.send_message(args) }
expect(&block).to make_telegram_request(bot, :sendMessage).with(hash_including(args))
expect(&block).to make_telegram_request(bot, :sendMessage).with(
text: a_string_matching(/est$/),
parseMode: :Markdown,
)
end
end
end
describe '#send_telegram_message' do
let(:args) { {text: 'test', parseMode: :Markdown} }
let(:block) { ->(*) { bot.send_message(args) } }
it 'works for matching' do
expect(&block).to send_telegram_message(bot, 'test')
expect(&block).to send_telegram_message(bot, /^tes/)
expect(&block).to send_telegram_message(bot, 'test', parseMode: :Markdown)
expect(&block).to send_telegram_message(bot, a_string_including('es'))
end
it 'works for not-matching' do
expect(&block).to_not send_telegram_message(bot, 'test!')
expect(&block).to_not send_telegram_message(bot, /tes$/)
expect(&block).to_not send_telegram_message(bot, 'test', parseMode: :HTML)
expect(&block).to_not send_telegram_message(bot, a_string_including('smth'))
end
end
end