Robert Pankowecki ???

/home/rupert/Dropbox/drug/wroclawrb/logos/drug-logo-name-rect-sq.png /home/rupert/Dropbox/drug/wroclawrb/krasnal.png /home/rupert/Dropbox/drug/arkency.png /home/rupert/Dropbox/Photos/rupert_outside_bw.jpeg

Standalone applications?

Really?

External services

Possible solutions

Possible solutions[No Testing]

/home/rupert/Dropbox/drug/no_testing.png

Possible solutions[library has test mode]

/home/rupert/Dropbox/drug/library_test.png

Possible solutions[adapter approach]

/home/rupert/Dropbox/drug/adapter_approach.png

Mem

/home/rupert/Dropbox/drug/i_do_adapter.jpg

Possible solutions[mocking web requests]

/home/rupert/Dropbox/drug/mocking.png

Possible solutions[external system test mode]

/home/rupert/Dropbox/drug/external_system_test.png

Possible solutions[Custom actor]

/home/rupert/Dropbox/drug/test_actor.png

[EM] Test Actors playing External System in Event Machine

[EM] ExternalSystem

require 'evma_httpserver'

class ExternalSystem < TestActor

  def initialize(*args)
    @messages = []
    EM.start_server("0.0.0.0", port, system, @env.server_options) do |c|
      c.messages = @messages
      after_initialize(c)
    end
  end

  def after_initialize(system_instance)
  end

  def received?(options = {}, &block)
    message = fetch_message(options[:timeout])
    yield message if block_given?
    message
  end

  def receive_message!(options = {}, &block)
    msg = received?(options)
    env.assert_not_nil msg
    yield msg if block_given?
    msg
  end

  protected

  def fetch_message(timeout = nil)
    timeout ||= @default_timeout
    f, message = Fiber.current, Observer.new(@messages, timeout)
    message.callback { |msg| f.resume(msg) }
    message.errback  { f.resume(nil) }
    return Fiber.yield
  end

  def system; raise NotImplementedError; end
  def port;   raise NotImplementedError; end
end

[EM] Observer

class Observer
  include EM::Deferrable

  def initialize(messages, observe_timeout)
    timeout(observe_timeout)

    @messages    = messages
    @shift_timer = EM.add_periodic_timer(0.001) { shift_message }

    self.callback { EM.cancel_timer(@shift_timer) }
    self.errback  { EM.cancel_timer(@shift_timer) }
  end

  def shift_message
    if msg = @messages.shift
      succeed(msg)
    end
  end
end

[EM] TestBulkSms

class TestBulkSms < ExternalSystem
  def system; Connection; end
  def port; 2198; end

  class Connection < EM::Connection
    include EM::HttpServer

    attr_accessor :messages

    def process_http_request
      response = EM::DelegatedHttpResponse.new(self)
      response.status = 200
      response.content_type 'text/html'
      response.content = case @http_path_info
        when "/eapi/submission/send_sms/2/2.0"    then "0|In progress"
        end
      response.send_response
      @messages << Message.new(@http_post_content)
    end
  end
end

[EM] Message

class Message
  attr_accessor :number, :message, :user, :password
  private :number=, :message=, :user=, :password=

  def initialize(content)
    hash = ActiveSupport::HashWithIndifferentAccess.new(
      CGI.parse(content).tap{|h| h.each{|k,v| h[k]= v.first }}
    )
    self.number =   hash[:msisdn]
    self.message =  hash[:message]
    self.user =     hash[:username]
    self.password = hash[:password]
  end
end

[EM] test scenario

class PrivateMessagesFeature < SecretProject::TestCase

  def test_private_message_push
    alice = TestUser.new("alice")
    bob   = TestUser.new("bob")
    apns  = TestAPNS.new(self)

    alice.send_private_message("hello", recipient: bob)

    apns.receive_notification! do |notification|
      assert_equal "New message from Alice", notification.alert
      assert_equal 1, notification.badge
    end
  end

end

[EM] Summary

[Rails] Test Actors playing External System in Ruby on Rails

[Rails] Test scenario

class CreditCardProcessingFeature < TopSecretSystem::TestCase

  def test_sends_data_to_cc
    extend Test::Unit::Assertions

    es = ExternalSystem.new(Servlet)

    # Pretend user clicking our webpage triggered
    # some event trying to communicate with external
    # web system processing credit cards
    Nestful.get 'http://localhost:1234?amount=123.45&card=111122223333444'

    es.receive_message! do |m|
      m.visa!
      assert_equal 123.45, m.amount
    end

    es.stop
  end

end

[Rails] External System

class ExternalSystem

  def initialize(servlet)
    messages = @messages = []
    mutex = @mutex = Mutex.new

    @thread = Thread.new do
      @server = WEBrick::HTTPServer.new(:Port => 1234)
      @server.mount "/", servlet, @mutex, @messages
      @server.start
    end
  end

  def stop
    @thread.exit
  end

  def received?(options ={}, &block)
    message = fetch_message(options[:timeout])
    yield message if block_given?
    message
  end

  def receive_message!(options = {}, &block)
    message = received?(options)
    assert_not_nil message
    yield message if block_given?
    message
  end

  def fetch_message(timeout = 5)
    o = Observer.new(@messages, @mutex, timeout)
    o.message
  end

end

[Rails] Observer

class Observer

  def initialize(collection, mutex, timeout)
    @collection = collection
    @mutex = mutex
    @timeout = timeout
  end

  def message
    Timeout.timeout(@timeout) do
      while true
        @mutex.synchronize do
          msg = @collection.shift
          return msg if msg
        end
        sleep 0.3
      end
    end
  rescue Timeout::Error
    return nil
  end

end

[Rails] Servlet

class Servlet < ::WEBrick::HTTPServlet::AbstractServlet

    def initialize(server, mutex, messages)
      super(server)
      @messages = messages
      @mutex = mutex
    end

    def do_GET(request, response)
      @mutex.synchronize do
        hash = ActiveSupport::HashWithIndifferentAccess[
          CGI.parse(request.query_string).map{|k,v| [k,v.first] }
        ]
        @messages << Message.new(hash[:amount].to_f, hash[:card])
      end
      response.status = 200
      response.content_type = "text/plain"
      response.body = "You are trying to load #{request.path}"
    end
  end

end

[Rails] Message

class Message < Struct.new(:amount, :credit_card)

  include Test::Unit::Assertions

  def visa?
    credit_card.to_s.starts_with?("1")
  end

  def visa!
    assert visa?
  end

end

[Rails] Summary