From 0c9a4a5fa00c17d4af888341c26e60396a045099 Mon Sep 17 00:00:00 2001 From: Max Melentiev Date: Wed, 16 Nov 2016 14:16:45 +0300 Subject: [PATCH] Inegration context for RSpec ActionDispatch::Testing::Integration requires smth else and defines Rails, so had to change `defined?(Rails)` all over the code. --- Gemfile | 2 +- README.md | 46 +++++++++----- lib/telegram/bot/config_methods.rb | 4 +- lib/telegram/bot/rspec/integration.rb | 49 +++++++++++++++ .../bot/updates_controller/rspec_helpers.rb | 14 ++++- lib/telegram/bot/updates_poller.rb | 6 +- spec/telegram/bot/rspec/integration_spec.rb | 62 +++++++++++++++++++ 7 files changed, 159 insertions(+), 24 deletions(-) create mode 100644 lib/telegram/bot/rspec/integration.rb create mode 100644 spec/telegram/bot/rspec/integration_spec.rb diff --git a/Gemfile b/Gemfile index d88e257..cb220c2 100644 --- a/Gemfile +++ b/Gemfile @@ -15,7 +15,7 @@ group :development do gem 'telegram-bot-types', '~> 0.2.0' - gem 'rspec', '~> 3.3.0' + gem 'rspec', '~> 3.5.0' gem 'rspec-its', '~> 1.1.0' gem 'rubocop', '~> 0.37.0' diff --git a/README.md b/README.md index 668ff5b..d8e8e87 100644 --- a/README.md +++ b/README.md @@ -323,36 +323,52 @@ To stub all possible clients use `Telegram::Bot::ClientStub.stub_all!` before initializing clients. Most likely you'll want something like this: ```ruby +# environments/test.rb +# Make sure to run it before defining routes or storing bot to some place in app! +Telegram.reset_bots +Telegram::Bot::ClientStub.stub_all! + +# rails_helper.rb RSpec.configure do |config| # ... - Telegram.reset_bots - Telegram::Bot::ClientStub.stub_all! config.after { Telegram.bot.reset } # ... end ``` -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: +There are integration and controller contexts for RSpec and some built-in matchers: ```ruby -include Telegram::Bot::RSpec::ClientMatchers # no need if you already use controller herlpers +# spec/requests/telegram_webhooks_spec.rb +require 'telegram/bot/rspec/integration' + +RSpec.describe TelegramWebhooksController, :telegram_bot do + # for old rspec add: + # include_context 'telegram/bot/integration' + + describe '#start' do + subject { -> { dispatch_command :start } } + it { should respond_with_message 'Hi there!' } + 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')) - -# 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. ``` +See sample app for more examples. + ### Deploying Use `rake telegram:bot:set_webhook` to update webhook url for all configured bots. diff --git a/lib/telegram/bot/config_methods.rb b/lib/telegram/bot/config_methods.rb index 6272e21..5712410 100644 --- a/lib/telegram/bot/config_methods.rb +++ b/lib/telegram/bot/config_methods.rb @@ -20,7 +20,7 @@ module Telegram def bot_poller_mode? return @bot_poller_mode if defined?(@bot_poller_mode) @bot_poller_mode = ENV.fetch('BOT_POLLER_MODE') do - Rails.env.development? if defined?(Rails) + Rails.env.development? if defined?(Rails.env) end end @@ -47,7 +47,7 @@ module Telegram # Can be overwritten with .bots_config= def bots_config @bots_config ||= - if defined?(Rails) + if defined?(Rails.application) telegram_config = Rails.application.secrets[:telegram] || {} (telegram_config['bots'] || {}).symbolize_keys.tap do |config| default = telegram_config['bot'] diff --git a/lib/telegram/bot/rspec/integration.rb b/lib/telegram/bot/rspec/integration.rb new file mode 100644 index 0000000..aebcf6a --- /dev/null +++ b/lib/telegram/bot/rspec/integration.rb @@ -0,0 +1,49 @@ +RSpec.shared_context 'telegram/bot/integration' do + let(:bot) { Telegram.bot } + let(:from_id) { 123 } + let(:chat_id) { 456 } + let(:default_message_options) { {from: {id: from_id}, chat: {id: chat_id}} } + let(:controller_path) do + route_name = Telegram::Bot::RoutesHelper.route_name_for_bot(bot) + Rails.application.routes.url_helpers.public_send("#{route_name}_path") + end + let(:request_headers) do + { + 'ACCEPT' => 'application/json', + 'Content-Type' => 'application/json', + } + end + let(:clear_session?) { described_class.respond_to?(:session_store) } + before { described_class.session_store.clear if clear_session? } + + include Telegram::Bot::RSpec::ClientMatchers + + def dispatch(update) + if ActionPack::VERSION::MAJOR >= 5 + post(controller_path, params: update.to_json, headers: request_headers) + else + 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) + 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.configure do |config| + if config.respond_to?(:include_context) + config.include_context 'telegram/bot/integration', :telegram_bot + end +end diff --git a/lib/telegram/bot/updates_controller/rspec_helpers.rb b/lib/telegram/bot/updates_controller/rspec_helpers.rb index 295c12f..1a4981a 100644 --- a/lib/telegram/bot/updates_controller/rspec_helpers.rb +++ b/lib/telegram/bot/updates_controller/rspec_helpers.rb @@ -13,6 +13,9 @@ RSpec.shared_context 'telegram/bot/updates_controller' do 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 @@ -20,9 +23,8 @@ RSpec.shared_context 'telegram/bot/updates_controller' do controller.dispatch_again(bot, update) end - def dispatch_message(text, options = nil) - options ||= respond_to?(:default_message_options) ? default_message_options : {} - update = build_update :message, options.merge(text: text) + def dispatch_message(text, options = {}) + update = build_update :message, default_message_options.merge(options).merge(text: text) dispatch bot, update end @@ -43,3 +45,9 @@ RSpec.shared_context 'telegram/bot/updates_controller' do send_telegram_message(bot, expected, chat_id: chat_id) end end + +RSpec.configure do |config| + if config.respond_to?(:include_context) + config.include_context 'telegram/bot/updates_controller', type: :telegram_bot_controller + end +end diff --git a/lib/telegram/bot/updates_poller.rb b/lib/telegram/bot/updates_poller.rb index 05e2b15..35b76da 100644 --- a/lib/telegram/bot/updates_poller.rb +++ b/lib/telegram/bot/updates_poller.rb @@ -27,12 +27,12 @@ module Telegram attr_reader :bot, :controller, :timeout, :offset, :logger, :running, :reload def initialize(bot, controller, **options) - @logger = options.fetch(:logger) { defined?(Rails) && Rails.logger } + @logger = options.fetch(:logger) { defined?(Rails.logger) && Rails.logger } @bot = bot @controller = controller @timeout = options.fetch(:timeout) { DEFAULT_TIMEOUT } @offset = options[:offset] - @reload = options.fetch(:reload) { defined?(Rails) && Rails.env.development? } + @reload = options.fetch(:reload) { defined?(Rails.env) && Rails.env.development? } end def log(&block) @@ -85,7 +85,7 @@ module Telegram end end - if defined?(Rails) && Rails.application.respond_to?(:reloader) + if defined?(Rails.application) && Rails.application.respond_to?(:reloader) def reloading_code Rails.application.reloader.wrap do yield diff --git a/spec/telegram/bot/rspec/integration_spec.rb b/spec/telegram/bot/rspec/integration_spec.rb new file mode 100644 index 0000000..091c43a --- /dev/null +++ b/spec/telegram/bot/rspec/integration_spec.rb @@ -0,0 +1,62 @@ +require 'telegram/bot/rspec/integration' +require 'action_controller' +require 'action_dispatch' +require 'action_dispatch/testing/integration' + +RSpec.describe 'Integrations helper', :telegram_bot do + include ActionDispatch::Integration::Runner + + let(:app) { Telegram::Bot::Middleware.new(bot, controller) } + let(:bot) { Telegram::Bot::ClientStub.new('token') } + let(:controller_path) { '/' } + let(:controller) do + Class.new(Telegram::Bot::UpdatesController) do + def start(*args) + respond_with :message, text: "Start: #{args.inspect}, option: #{payload[:option]}" + end + end + end + + describe '#default_message_options' do + subject { default_message_options } + it { should eq from: {id: from_id}, chat: {id: chat_id} } + end + + describe '#dispatch' do + subject { -> { dispatch message: {text: '/start', **default_message_options} } } + it { should respond_with_message 'Start: [], option: ' } + end + + describe '#dispatch_message' do + subject { -> { dispatch_message "/start #{args.join ' '}", options } } + let(:args) { %w(asd qwe) } + let(:options) { {} } + it { should respond_with_message "Start: #{args.inspect}, option: " } + + context 'with options' do + let(:options) { {option: 1} } + it { should respond_with_message "Start: #{args.inspect}, option: 1" } + + context 'and chat_id is not set' do + let(:options) { super().merge(chat: nil) } + it { should raise_error(/chat is not present/) } + end + end + end + + describe '#dispatch_command' do + subject { -> { dispatch_command :start, *args } } + let(:args) { [] } + it { should respond_with_message "Start: #{args.inspect}, option: " } + + context 'with args' do + let(:args) { %w(asd qwe) } + it { should respond_with_message "Start: #{args.inspect}, option: " } + end + + context 'with options' do + let(:args) { ['asd', 'qwe', option: 1] } + it { should respond_with_message "Start: #{args[0...-1].inspect}, option: 1" } + end + end +end