How to stub class instantiated inside tested class in rspec - ruby

I have problem stubbing external api, following is the example
require 'rspec'
require 'google/apis/storage_v1'
module Google
class Storage
def upload file
puts '#' * 90
puts "File #{file} is uploaded to google cloud"
end
end
end
class UploadWorker
include Sidekiq::Worker
def perform
Google::Storage.new.upload 'test.txt'
end
end
RSpec.describe UploadWorker do
it 'uploads to google cloud' do
google_cloud_instance = double(Google::Storage, insert_object: nil)
expect(google_cloud_instance).to receive(:upload)
worker = UploadWorker.new
worker.perform
end
end
I'm trying to stub Google::Storage class. This class is instantiated inside the object being tested. How can I verify the message expectation on this instance?
When I run above example, I get following output, and it seems logical, my double is not used by tested object
(Double Google::Storage).upload(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
I'm new to Rspec and having hard time with this, any help will be appreciated.
Thanks!

Reaching for DI is always a good idea (https://stackoverflow.com/a/51401376/299774) but there are sometimes reasons you can't so it, so here's another way to stub it without changing the "production" code.
1. expect_any_instance_of
it 'uploads to google cloud' do
expect_any_instance_of(Google::Storage).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
In case you just want to test that the method calls the method on any such objects.
2. bit more elaborated setup
In case you want to control or set up more expectations, you can do this
it 'uploads to google cloud' do
the_double = instance_double(Google::Storage)
expect(Google::Storage).to receive(:new).and_return(the_double)
# + optional `.with` in case you wanna assert stuff passed to the constructor
expect(the_double).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
Again - Dependency Injection is clearer, and you should aim for it. This is presented as another possibility.

I would consider reaching for dependency injection, such as:
class UploadWorker
def initialize(dependencies = {})
#storage = dependencies.fetch(:storage) { Google::Storage }
end
def perform
#storage.new.upload 'test.txt'
end
end
Then in the spec you can inject a double:
storage = double
expect(storage).to receive(...) # expection
worker = UploadWorker.new(storage: storage)
worker.perform
If using the initializer is not an option then you could use getter/setter method to inject the dependency:
def storage=(new_storage)
#storage = new_storage
end
def storage
#storage ||= Google::Storage
end
and in the specs:
storage = double
worker.storage = storage

Related

Rspec: Difference between allow and allow_any_instance_of

I have a simple MySQL wrapper class which will run a query and return results.
class Rsql
def initialize(db)
#client = Mysql2::Client
#db = db
end
def execute_query()
client = #client.new(#db)
client.query("select 1")
end
end
I want to test some stuff involving the results of the query, but I don't want to actually connect to a database to get the results. I tried this test, but it doesn't work:
RSpec.describe Rsql do
it "does it" do
mock_database = double
rsql = Rsql.new(mock_database)
mock_mysql_client = double
allow(mock_mysql_client).to receive(:query).and_return({"1" => 1})
allow_any_instance_of(Mysql2::Client).to receive(:new).and_return(mock_mysql_client)
expect(rsql.execute_query).to eq({"1" => 1})
end
end
Replacing allow_any_instance_of() with allow() works. I was under the impression that allow_any_instance_of() was some kind of a global "pretend this class behaves in this way across the entire program" whereas allow() is for specific instances of a class.
Can someone explain this behavior to me? I'm new to Rspec, so I apologize if this answer is blatantly obvious. I tried searching for the answer, but I couldn't come up with the right search string to find one. Maybe I don't know enough to know when I've found it.
As of RSpec 3.3 , any_instance is deprecated and not recommended to use in your tests.
From the docs:
any_instance is the old way to stub or mock any instance of a class
but carries the baggage of a global monkey patch on all classes. Note
that we generally recommend against using this feature.
You should only need to use allow(some_obj) going forward and the documentation has some great examples (see here).
Such as:
RSpec.describe "receive_messages" do
it "configures return values for the provided messages" do
dbl = double("Some Collaborator")
allow(dbl).to receive_messages(:foo => 2, :bar => 3)
expect(dbl.foo).to eq(2)
expect(dbl.bar).to eq(3)
end
end
Edit, if you really want to use any_instance, do so like this:
(Mysql2::Client).allow_any_instance.to receive(:something)
Edit2, your exact stub doesn't work because you're not stubbing an instance, you're stubbing before the object is initialized. In that case you would do allow(Mysql2::Client).to receive(:new).
this Rsql class seems a service
class Rsql
def initialize(db)
#client = Mysql2::Client
#db = db
end
def execute_query()
client = #client.new(#db)
client.query("select 1")
end
end
lets create a test for it, now we should to test this function execute_query with subject ()
and to create clients in db we can use let! like this
let!(:client1) do
FactoryBot.create(...
with this we should not use double or something
require 'rails_helper'
RSpec.describe RsqlTest do
subject(:clients) do
Rsql.execute_query()
end
context 'select' do
let!(:client1) do
FactoryBot.create(...
end
it 'should return records' do
expect(clients).to include(client1)
end
end
end

Calling a Volt Framework Task method from another Task

I have a Volt Framework Task that checks and stores information on a directory, e.g.
class DirectoryHelperTask < Volt::Task
def list_contents()
contents = []
Dir.glob("/path/to/files").each do |f|
contents << f
end
return contents
end
end
I would like to call this from a different task, e.g.
class DirectoryRearrangerTask < Volt::Task
dir_contents = DirectoryHelperTask.list_contents()
end
The code above (DirectoryRearranger) throws an error, as does a promise call
DirectoryHelperTask.list_contents().then do |r|
dir_conents = r
end.fail do |e|
puts "Error: #{e}"
end
Could not find a way to call a task from another task in the Volt Framework documentation.
Thanks a lot!
From what I gather, tasks are meant to be run on the server side and then called on the client side, hence the use of the promise object. The promise object comes from OpalRb, so trying to call it from MRI won't work. If you have a "task" that will only be used on the server side, then it doesn't really fit with Volt's concept of a task.
Your first approach to the problem actually does work, except that DirectoryRearrangerTask can't inherit from Volt::Task.
directory_helper_task.rb
require_relative "directory_rearranger_task"
class DirectoryHelperTask < Volt::Task
def list_contents
contents = []
Dir.glob("*").each do |file|
contents << file
end
DirectoryRearrangerTask.rearrange(contents)
contents
end
end
directory_rearranger_task.rb
class DirectoryRearrangerTask
def self.rearrange(contents)
contents.reverse!
end
end
Here is a GitHub repo with my solution to this problem.
You can call tasks from the client or server, but keep in mind that you call instance methods on the class. (So they get treated like singletons) And all methods return a Promise. I think your issue here is that your doing dir_contents = DirectoryHelperTask.list_contents() inside of the class. While you could do this in ruby, I'm not sure its what you want.
Also, where you do dir_contents = r, unless dir_contents was defined before the block, its going to get defined just in the block.

How do I test (TDD) a singleton class?

I am starting with DDD and TDD in a Ruby application using Minitest.
I created a repository class (no database access, but it generates the entities for me). It is a singleton.
I would like to test the generation of the entities. The problem is that because it is a singleton, the order of executions of the tests affect the results.
Is there any way to force the disposal of the singleton element so it is "fresh"?
Here is my repository code:
require "singleton"
class ParticipantRepository
include Singleton
def initialize()
#name_count = 0
end
def generate_participant()
participant = Participant.new
participant.name = "Employee#{get_name_count()}"
return participant
end
private
def get_name_count()
old_name_count = #name_count
#name_count += 1
return old_name_count
end
end
And the tests:
require_relative 'test_helper'
class ParticipantRepositoryTest < MiniTest::Unit::TestCase
def setup()
#repository = ParticipantRepository.instance
end
def test_retrieve_participant
participant = #repository.generate_participant
refute_nil participant
refute_nil participant.name
refute_equal("", participant.name)
assert_equal(0, participant.subordinates_count)
end
def test_employee_name_increment
participant1 = #repository.generate_participant
participant2 = #repository.generate_participant
refute_equal(participant1.name, participant2.name)
index_participant1 = /Employee([0-9]+)/.match(participant1.name)[1]
index_participant2 = /Employee([0-9]+)/.match(participant2.name)[1]
assert_equal(0, index_participant1.to_i)
assert_equal(1, index_participant2.to_i)
end
end
The assertion assert_equal(0, index_participant1.to_i) succeeds when test_employee_name_increment is executed first and fails if it is executed last.
I would like to be able to test the repository (because it will evolve into something bigger). How can I do that?
Thanks!
Ordering your tests won't matter. To properly test a singleton class you need to treat it like an instance object. To do that, wrap your singleton in an anonymous Class during setup. Every time setup is called you'll get an untouched copy of the ParticipantRepository:
def setup
#repository = Class.new(ParticipantRepository).instance
end
Call i_suck_and_my_tests_are_order_dependent!() at the top of your
tests when you absolutely positively need to have ordered tests. In
doing so, you’re admitting that you suck and your tests are weak.
...I promise I didn't write this method or the docs.
For more info, see: http://www.ruby-doc.org/stdlib-2.0/libdoc/minitest/rdoc/MiniTest/Unit/TestCase.html#method-c-i_suck_and_my_tests_are_order_dependent-21

ruby, no method error

I am receiving the following error when running my below ruby script:
s3parse.rb:12:in `block in <class:AccountLog>': undefined method `extract_account_id' for AccountLog:Class (NoMethodError)
I dont think it should be a class method, is there a reason its not taking my method into account?
class AccountLog
attr_accessor :bytes, :account_id, :date
def extract_account_id(line)
line.match(%r{accounts/(\d+)}).captures.join.to_i
end
s3log = File.open('vidcoder.txt').each do |line|
account_log = AccountLog.new
account_log.date = line.match(%r{\[[^:]*}).to_s.delete"[" #need to finish this regex to make it work
account_log.account_id = extract_account_id(line)
account_log.bytes = line.match(%r{^.*\s+HTTP.*\s+-\s+(\d+)\s+}).captures.join.to_i
puts "\n"
puts "The api request on #{account_log.date} was fromm account number #{account_log.account_id} and the bytes were #{account_log.bytes}"
end
end
def extract_account_id will define an instance method.
In the way you call it, you need a class method instead.
Define it like this:
def self.extract_account_id(line)
or, as you already have an AccountLog instance, use it to call extract_account_id:
account_log.account_id = account_log.extract_account_id(line)
Please note that with second way you do not need to alter method definition, just call extract_account_id via account_log instance.
And i guess you would want to put s3log = File... outside class definition.
Or use a constant instead: S3log = ...
Then you'll can access it as AccountLog::S3log
Is there any reason you don't think it should be a class method? You are using it in the context of a class method and that's why it it's saying no such method for class AccountLog.
If you name your method as self.extract_account_id(line) I'm sure it will work.
From what you are trying to do I think this is what you are looking for?
class AccountLog
attr_accessor :bytes, :account_id, :date
def self.extract_account_id(line)
line.match(%r{accounts/(\d+)}).captures.join.to_i
end
end
s3log = File.open('vidcoder.txt').each do |line|
account_log = AccountLog.new
account_log.date = line.match(%r{\[[^:]*}).to_s.delete"[" #need to finish this regex to make it work
account_log.account_id = extract_account_id(line)
account_log.bytes = line.match(%r{^.*\s+HTTP.*\s+-\s+(\d+)\s+}).captures.join.to_i
puts "\n"
puts "The api request on #{account_log.date} was fromm account number #{account_log.account_id} and the bytes were #{account_log.bytes}"
end
While you could take the class method approach, there seems to be a little more going on.
You should put the extraction logic in a method in itself rather than let it hangout in your class. Then outside of the class, have an instance of AccountLog where you can call on the methods for log and account id extraction. At that point you can do something with those values.
Class method or not is a detail we can explore after the class is a bit more clean I think.

What is the best way to define test specs in JSON using a Ruby harness?

I've got an interesting conundrum. I'm in the midst of developing a library to parse PSDs in Ruby. Also, a buddy is simultaneously working on a library to parse PSDs in JavaScript. We would like to share the same unit tests via a git submodule.
We've decided to use a simple JSON DSL to define each test. A single test might look like:
{
"_name": "Layer should render out",
"_file": "test/fixtures/layer_out.psd",
"_exports_to": "test/controls/layer_out_control.png"
}
So, now it's up to us to build the appropriate test harnesses to translate the JSON into the appropriate native unit tests. I've been using MiniTest to get myself up to speed, but I'm running into a few walls.
Here's what I've got so far. The test harness is named TargetPractice for the time being:
# run_target_practice.rb
require 'target_practice'
TargetPractice.new(:test) do |test|
test.pattern = "test/**/*.json"
end
and
# psd_test.rb
class PSDTest < MiniTest::Unit::TestCase
attr_accessor :data
def tests_against_data
# do some assertions
end
end
and
# target_practice.rb
class TargetPractice
attr_accessor :libs, :pattern
def initialize(sym)
#libs = []
#pattern = ""
yield self
run_tests
end
def run_tests
FileList[#pattern].to_a.each do |file|
test_data = JSON.parse(File.open(file).read)
test = PSDTest.new(test_data["_name"]) do |t|
t.data = test_data
end
end
end
end
Unfortunately, I'm having trouble getting a yield in the initialize to stick in my PSDTest class. Also, it appears that a test will run immediately on initialization.
I would like to dynamically create a few MiniTest::Unit::TestCase objects, set their appropriate data properties and then run the tests. Any pointers are appreciated!
I think you are overcomplicating things a bit here. What you need is a parameterized test, which is pretty trivial to implement using mintest/spec:
describe "PSD converter" do
def self.tests(pattern = 'test/**/*.json')
FileList[pattern].map{|file| JSON.parse(File.read(file))}
end
tests.each do |test|
it "satisfies test: " + test["_name"] do
# some assertions using test["_file"] and test["_exports_to"]
end
end
end

Resources