Mocks & Stubs & Exceptions in Ruby

  • stubbing
  • mocking
  • raising exceptions
  • rescuing exceptions
  • testing all of the above

What are mocks and stubs?

  • mocks are objects that have a similar interface as something else
  • stubs are fake methods and return a specific answer

Eliminate dependencies on other classes in testing

class Connection

attr_reader :status_code

def initialize
@status_code = get_service_status
end

def get_service_status
200
end

def connect_to_external_service(srvc)
return srvc.status_code
end

end
class ConnectionTest < Minitest::Test

def test_initializes_with_response_code
c = Connection.new
assert_equal 200, c.status_code
end

end
class Connection

attr_reader :status_code

def initialize
@status_code = get_service_status
end

def get_service_status
200
end


def connect_to_external_service(srvc)
return srvc.status_code
end

end

# this is my third-party class that `Connection` might have dependencies upon
class Service
attr_reader :status_code

def initialize
@status_code = get_service_status
# status_code would be 200, 504, 404, etc
end

def get_service_status
# do complicated stuff here to get status code
# return a status code
200
end
end
srvc = Service.new
conn = Connection.new

conn.connect_to_external_service(srvc)
class ConnectionTest < Minitest::Test

def test_connection_default_status_code_is_200
c = Connection.new
assert_equal 200, c.status_code
end

def test_external_service_default_status_code_is_200
conn = Connection.new
srvc = Service.new

assert_equal 200, conn.connect_to_external_service(srvc)
end
end

Stubbing the service object to force errors

def test_raises_error_when_service_returns_4xx_no_mocks
srvc = Service.new
srvc.status_code = 404
conn = Connection.new

assert_raises Connection::ServiceNotFound do
conn.connect_to_external_service(srvc)
end
end
1) Error:
ConnectionTest#test_raises_error_when_service_returns_4xx_no_mocks:
NoMethodError: undefined method `status_code=' for #<Service:0x00007f81ce3c34d8 @status_code=200>
require './lib/connection'
require 'minitest/autorun'
require 'mocha/minitest'

class ConnectionTest < Minitest::Test

def test_raise_error_when_service_returns_4xx_using_stubs
serv = Service.new
# instantiating a new Service object. if I call
# serv.status_code, it would return 200
Service.any_instance.stubs(:status_code).returns(404)
# override the method to always return 404
conn = Connection.new

assert_raises Connection::ServiceNotFound do
conn.connect_to_external_service(serv)
end
end

Mocking the service object

def test_rais_error_when_service_returns_4xx_using_mocks
conn = Connection.new
srvc = stub("srvc is now a stub.")
# srvc.class == Mocha::Mock

srvc.stubs(:status_code).returns(404)

assert_raises Connection::ServiceNotFound do
conn.connect_to_external_service(srvc)
end
end

You’ll never not necessarily see the word ‘mock’ in your code, even when using mocks.

mocked_object = mock('mocked_object')
stubbed_object = stub('stubbed_object')
> mocked_object
=> #<Mock:mocked_object>
> stubbed_object
=> #<Mock:stubbed_object>

Exceptions

def test_raise_service_timeout_if_service_returns_504
@srv.stubs(:status_code).returns(504)

assert_raises Connection::ServiceTimeOut do
@conn.connect_to_external_service(@srv)
end
end
def connect_to_external_service(srvc)
raise ServiceNotFound if srvc.status_code == 404
raise ServiceTimeOut if srvc.status_code == 504
# do other stuff
return srvc.status_code
end
.
.
def connect_to_external_service(srvc)
raise ServiceNotFound if srvc.status_code == 404
raise ServiceTimeOut if srvc.status_code == 504
# do other stuff
return srvc.status_code
end

class ServiceNotFound < StandardError; end
class ServiceTimeOut < StandardError; end
end
ServiceNotFound.ancestors
=> [Connection::ServiceNotFound,
StandardError,
Exception,
Object,
Kernel,
BasicObject]
StandardError.ancestors
=> [StandardError,
Exception,
Object,
Kernel,
BasicObject]

Misc Resources

--

--

--

Software, rock climbing, books. I love to learn. https://josh.works

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Using cURL API calls with PHP

Most Common Difficulties Confronted by Android App Developers

So Easy MicroPython — MQTT ThingsBoard, IoT

Parameter Store and CloudFormation

▶️ ARTH — Task 7 💻

Seven Minutes of Hell and the Last Mile Phenomenon

5 Ways AssetSonar Can Facilitate An Effective Remote Work Policy

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Josh Thompson

Josh Thompson

Software, rock climbing, books. I love to learn. https://josh.works

More from Medium

Rails Project Reflection — A Ruby on Rails Recipe Manager

Ensure only one record has specific value for another associated record | Rails 6.1.4

Abstract Classes and Why We Use Them with Active Record in Our Rails Application

Save Data Automatically with Ruby on Rails