Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration test #209

Open
josephktcheung opened this issue Oct 15, 2019 · 6 comments
Open

Integration test #209

josephktcheung opened this issue Oct 15, 2019 · 6 comments

Comments

@josephktcheung
Copy link

josephktcheung commented Oct 15, 2019

Hi,

Related to #83, I'd like to share how I write Stealth's integration test. Source code can be found here https://github.com/josephktcheung/stealth-integration-test. @luizcarvalho @mgomes please take a look and see if this can be improved.

Steps:

  1. Run stealth new to generate a new stealth app

  2. Install following gems for testing

group :test do
  gem "rack-test"
  gem "rspec"
  gem "mock_redis"
end
  1. In spec/spec_helper.rb
# coding: utf-8
# frozen_string_literal: true


require 'rspec'
require 'stealth'
require 'mock_redis'
require 'sidekiq/testing'

# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'bot'))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'config'))
$LOAD_PATH.unshift(File.dirname(__FILE__))

Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
require_relative "../bot/helpers/bot_helper"

ENV['STEALTH_ENV'] = 'test'

RSpec.configure do |config|
  I18n.load_path += Dir[File.join(File.dirname(__FILE__), '..', 'config', 'locales', '*.{rb,yml}')]
  config.include BotHelper
  config.before(:each) do |example|
    Sidekiq::Worker.clear_all
    Sidekiq::Testing.fake!
    $redis = MockRedis.new
    allow(Redis).to receive(:new).and_return($redis)
  end
  config.filter_run_when_matching :focus
  config.formatter = :documentation

  config.before(:suite) do
    Stealth.boot
  end
end
  1. Define sample_message class in spec/support/sample_message.rb
class SampleMessage

  def initialize(service:)
    @service = service
    @base_message = Stealth::ServiceMessage.new(service: @service)
    @base_message.sender_id = sender_id
    @base_message.timestamp = timestamp
    @base_message
  end

  def message_with_text(message)
    @base_message.message = message
    self
  end

  def message_with_payload(payload)
    @base_message.payload = payload
    self
  end

  def message_with_location(location)
    @base_message.location = location
    self
  end

  def message_with_attachments(attachments)
    @base_message.attachments = attachments
    self
  end

  def sender_id
    "8b3e0a3c-62f1-401e-8b0f-615c9d256b1f"
  end

  def timestamp
    @base_message.timestamp || Time.now
  end

  def to_request_json
    if @base_message.message.present?
      JSON.generate({
        entry: [
          {
            "messaging": [
              "sender": {
                "id": @base_message.sender_id
              },
              "recipient": {
                "id": "<PAGE_ID>"
              },
              "timestamp": @base_message.timestamp.to_i * 1000,
              "message": {
                "mid":"mid.1457764197618:41d102a3e1ae206a38",
                "text": @base_message.message
              }
            ]
          }
        ]
      })
    end
  end
end
  1. Define custom matcher send_reply in spec/support/matchers/send_reply.rb (Thanks @sunny for correction)
RSpec::Matchers.define :receive_message do |message|
  match do |client|
    stub = double("client")
    allow(stub).to receive(:transmit).and_return(true)
    @replies.each do |reply|
      expect(client).to receive(:new)
        .with(hash_including(reply: hash_including(reply)))
        .ordered
        .and_return(stub)
    end

    json = message.to_request_json
    post "/incoming/#{@service}", json, { "CONTENT_TYPE" => "application/json" }
  end

  chain :as_service do |service|
    @service = service
  end
  
  chain :and_send_replies do |replies|
    @replies = replies
  end
end
  1. In spec/features/chatbot_flow_spec.rb
require "spec_helper"

describe "chatbot flow" do
  include Rack::Test::Methods

  def app
    Stealth::Server
  end

  let(:message) { 
    SampleMessage.new(
      service: "facebook"
    )
  }

  let(:client) { Stealth::Services::Facebook::Client }

  it "handles user conversation" do
    Sidekiq::Testing.inline! do
      expect(client).to receive_message(
        message.message_with_text("hello")
      )
        .as_service("facebook")
        .and_send_replies([
          {
            "recipient" => {
              "id" => message.sender_id
            },
            "message" => {
              "text" => "Hello World!"
            }
          },
          {
            "recipient" => {
              "id" => message.sender_id
            },
            "message" => {
              "text" => "Goodbye World!"
            }
          }
        ])
    end
  end
end
  1. In bot/controllers/hellos_controller.rb
class HellosController < BotController

  def say_hello
    send_replies
    step_to flow: "goodbye"
  end

end
  1. Run bundle e rspec and test passes
@josephktcheung
Copy link
Author

And we can chain multi-step conversation like this:

expect(client).to receive_message(
  message.message_with_text("hello")
)
  .as_service("facebook")
  .and_send_replies([
    {
      "recipient" => {
        "id" => message.sender_id
      },
      "message" => {
        "text" => "Hello World!"
      }
    },
    {
      "recipient" => {
        "id" => message.sender_id
      },
      "message" => {
        "text" => "What's your name?"
      }
    }
  ])

expect(client).to receive_message(
  message.message_with_text("Luke Skywalker")
)
  .as_service("facebook")
  .and_send_replies([
    {
      "recipient" => {
        "id" => message.sender_id
      },
      "message" => {
        "text" => "Nice to meet you Luke Skywalker!"
      }
    },
    {
      "recipient" => {
        "id" => message.sender_id
      },
      "message" => {
        "text" => "Goodbye World!"
      }
    }
  ])

@sunny
Copy link
Contributor

sunny commented Jun 22, 2020

👏

Thanks for sharing this! This needs a page in the docs, perhaps?

To load spec/matchers/send_reply.rb, spec_helper.rb probably also needs:

Dir["#{File.dirname(__FILE__)}/matchers/**/*.rb"].each { |f| require f }

Also, is the JSON from SampleMessage specific to Facebook's webhook?

@rahulkeerthi
Copy link

Hi @sunny - @josephktcheung's matchers folder is in the support folder so not needed in this case as it's loaded via the ** wildcard. You can see the folder structure here.

I'm just playing around right now and I think JSON is Facebook-specific as Twilio webhooks use TwiML (its own flavour of XML) via the twilio-ruby gem.

Also hello fellow LWer 👋

@sunny
Copy link
Contributor

sunny commented Jun 28, 2020

Hey @rahulkeerthi, great to see more people from the Le Wagon family :)

matchers folder is in the support folder so not needed in this case

Ah, thanks! I followed this issue's description, it may need a little fix, then:

-4. Define custom matcher `send_reply` in `spec/matchers/send_reply.rb`
+4. Define custom matcher `send_reply` in `spec/support/matchers/send_reply.rb`

@rahulkeerthi
Copy link

I followed this issue's description

Ah, I missed that - you're right! 👍

@gorandev
Copy link

gorandev commented Mar 1, 2023

Hi @josephktcheung, I'm trying to implement this but it won't work, I'm getting

     Failure/Error:
       expect(client).to receive(:new)
         .with(hash_including(reply: hash_including(reply)))
         .ordered
         .and_return(stub)

       (Stealth::Services::Facebook::Client (class)).new(hash_including(:reply=>"hash_including(\"recipient\"=>{\"id\"=>\"8b3e0a3c-62f1-401e-8b0f-615c9d256b1f\"}, \"message\"=>{\"text\"=>\"Hello World!\"})"))
           expected: 1 time with arguments: (hash_including(:reply=>"hash_including(\"recipient\"=>{\"id\"=>\"8b3e0a3c-62f1-401e-8b0f-615c9d256b1f\"}, \"message\"=>{\"text\"=>\"Hello World!\"})"))
           received: 0 times
     # ./spec/support/matchers/send_reply.rb:6:in `block (3 levels) in <top (required)>'

I'm looking into the source code for Stealth and Stealth::Facebook to figure out what might have changed between when you wrote this and now, and I was wondering if you were still around?

Thank you for reading! Will post back here if I solve it on my own. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants