How to test if a DelayedJob is calling a method from another class? - ruby

I'm setting up specs to test whether or not a job calls a method under certain conditions.
This is what I have so far:
describe RandomJob do
context "when payload[:type] = MyModel" do
let!(:my_model) { create :my_model }
let!(:payload) { { type: "MyModel", id: my_model.id } }
context "when Model exists" do
it "calls MyModel.fire! with payload" do
RandomJob.perform_now(payload)
expect_any_instance_of(MyModel).to receive(:fire!).with(payload)
end
end
context "when Model does not exist" do
it "does not call MyModel.fire!" do
RandomJob.perform_now(payload)
expect_any_instance_of(MyModel).not_to receive(:fire!)
end
end
end
end
Just to be sure my way of testing worked. I setup my job like this:
class RandomJob < ApplicationJob
def perform(payload)
#payload = payload
fire_model!
end
private
def fire_model!
my_model&.fire! #payload
end
def my_model
MyModel.find(#payload[:id])
end
end
I expected the first test to pass, and the second to fail. However, my first test is failing while the second is passing.
What am I doing wrong?

You have to put the expectation before the perform_now call.
context "when Model exists" do
it "calls MyModel.fire! with payload" do
expect_any_instance_of(MyModel).to receive(:fire!).with(payload)
RandomJob.perform_now(payload)
end
end
context "when Model does not exist" do
it "does not call MyModel.fire!" do
expect_any_instance_of(MyModel).not_to receive(:fire!)
RandomJob.perform_now(payload)
end
end

Related

How to write rspec for logic and write mock STDIN

I am a very new coder and trying to write rspec for a class that test the conditional statement/logic. I started sudo coding for it but I was told to make mock STDIN which I don't know how to. Can someone please write the rspec for the class or give me a few idea how to create a mock STDIN. I need help writing rspec for the conditional statement/logic, if some can please just write the test for one of the context then I can do rest based on that.
require 'rails_helper'
module BAB::ACA
RSpec.describe partfinder do
describe '#find_part_id' do
let(:face) { create(:face) }
subject { described_class.find_part_id(face) }
context 'When bab con already exists' do
context 'when there are more than one part ids' do
#create part ids
context 'when user input matches an existing id' do
#mock STDIN that matches an existing, subject should equal that id
end
context 'when user input does not match an existing id' do
# mock STDIN that does match existing id, should return failure message
end
end
context 'when there is only one bab part id' do
# subject should equal the one that already exists
end
end
context 'when av con does not yet exist' do
# mock STDIN and make sure subject equals what you mocked
end
end
end
module BAB::ACA
class partfinder
def self.find_part_id(face)
av_con = BAB::Child:Fail.find_by(
face: face
reg: BAB:Child.find_reg
)
if av_con
look_id(face, av_con)
end
else
puts "What is #{face.name} BAB part id? must be 6"
STDIN.gets.chomp
end
end
def self.look_id(face, av_con)
if av_con.part_ids.length > 1
ask_for_id(face, av_con)
else
av.con.part_ids.first
end
end
def self.ask_for_id(face, av_con)
puts "What is #{face.name} BAB part id? "
bab_part_id = STDIN.gets.chomp
unless av.con.part_ids.include?(bab_part_id)
fail 'Entered id doesn't match'
end
bab_part_id
end
end
end
You can use method stubs.
In this case you want to stub STDIN.gets.chomp, so you'd do something like this:
describe '#find_part_id' do
before do
allow(STDIN.gets).to receive(:chomp).and_return(stdin_input)
end
let(:stdin_input) { 'user input from stdin' }
let(:face) { create(:face) }
subject { described_class.find_part_id(face) }
context 'When bab con already exists' do
context 'when there are more than one part ids' do
it 'some test' do
# your test here
end
end
# more contexts...
context 'a context that needs a different stdin_input' do
let(:stdin_input) { 'some different user input from stdin' }
it 'another test' do
# your test here
end
end
end
end
Where stdin_input is the string you want the user to enter for your tests.

Testing with Rspec - The correct way

My weakest point when it comes to coding, is using TDD & BDD methods - I tend to just write code.. but it is something that I am trying to work on.
Could anyone point out the best way to go about the following problem:
Class1:
module TempMod
class MyClass
def initalize(config)
#config = config
end
def process(xml)
if react_upon? xml.something
puts 'yeah'
else
puts 'nah'
end
end
def react_upon?(xml_code)
#code here
end
end
end
So lets say I wanted to test this class, or build it from a TDD point of view so I write my tests:
describe TempMod::MyClass do
let(:config) {double}
let(:myclass) {TempMod::MyClass.new config}
context 'Given that the xml is something we react upon' do
it 'should check that it is valid' do
myclass.process '<some><xml>here</xml></some>'
end
it 'should output yea'
end
end
How do I test that it is calling the react_upon? method. Do I even want to see it is calling it?
Is the proper way to test it, to test all the functions like the react_upon? itself independently of the other functions?
This is properly the main thing that is most confusing me with this sort of testing. Am I testing the whole class, or just individually testing the functions, and not their interactions with the other functions in that class?
Also I realize the the react_upon? might not adhere to the Single responsibility principle and I would probably move that out to its own module/class which I could test using a stub.
If anyone can shed some light on this for me that would be awesome.
edit:
describe TempMod::MyClass do
let (:valid_planning_status_xml) {
'<StatusUpdate> <TitleId>2329</TitleId> <FromStatus>Proposed</FromStatus> <ToStatus>Confirmed</ToStatus> </StatusUpdate>'
}
let(:config) { double }
let(:status_resolver) { double }
subject(:message_processor) { TempMod::MyClass.new config, status_resolver }
context 'Given that the message XML is valid' do
it 'should check the context of the message' do
expect(message_processor.process valid_planning_status_xml).to call :check_me
end
context 'Given that the message is for a planning event update' do
it 'should call something' do
pending
end
end
context 'Given that the message is for a recording job update' do
end
context 'Given that the message is for a video title update' do
end
end
end
Your question confused me a bit is this what you are asking
module TempMod
class MyClass
def initalize(config)
#config = config
end
def process(xml)
react_upon?(xml.something) ? 'yeah' : 'nah'
end
def react_upon?(xml_code)
#code here
end
end
end
Then test like
describe TempMod::MyClass do
let(:config) {double}
let(:myclass) {TempMod::MyClass.new config}
context 'Given that the xml is something we react upon' do
it "should respond to react_upon?" do
expect(myclass).to respond_to(:react_upon?)
end
it "should react_upon? valid xml" do
expect(myclass.react_upon?(YOUR VALID REACTION GOES HERE)).to be_true
end
it "should not react_upon? invalid xml" do
expect(myclass.react_upon?(YOUR INVALID REACTION GOES HERE)).to be_false
end
it "should say 'yeah' if it is valid" do
expect(myclass.process('<some><xml>here</xml></some>')).to eq('yeah')
end
it "should say 'nah' if it is invalid" do
expect(myclass.process('<some><xml>here</some>')).to eq('nah')
end
it 'should check the context of the message' do
expect(myclass).to receive(:react_upon?).with('<some><xml>here</xml></some>')
myclass.process('<some><xml>here</xml></some>')
end
end
end
Right now your tests have no expectations so I added one that expects myclass to respiond_to the react_upon? method and another that expects myclass.process(xml) to respond with a String that equals yeah.

Issue stubbing with RSpec

I am trying to understand why the result of these tests, the first test claims the method is not stubbed, however, the 2nd one is.
class Roll
def initialize
install if !installed?
end
def install; puts 'install'; end
end
describe Roll do
before do
class RollTestClass < Roll; end
RollTestClass.any_instance.stub(:install)
end
let(:roll_class) { RollTestClass }
let(:roll) { RollTestClass.new }
context 'when installed is true' do
before do
roll_class.any_instance.stub(:installed?).and_return(true)
end
it 'should not call install' do
expect(roll).to_not have_received(:install)
end
end
context 'when installed is false' do
before do
roll_class.any_instance.stub(:installed?).and_return(false)
end
it 'should call install' do
expect(roll).to have_received(:install)
end
end
end
It's also strange the error says expected to have received install, but I think that is likely just faulty feedback from the RSpec DSL. But maybe worth noting.
1) Roll when installed is true should not call install
Failure/Error: expect(roll).to_not have_received(:install)
#<RollTestClass:0x10f69ef78> expected to have received install, but that method has not been stubbed.
The "spy pattern" of RSpec requires that the objects have been previously stubbed. However, any_instance.stub doesn't actually stub the methods "for real" unless/until the method is invoked on a particular object. As such, the methods appears as being "unstubbed" and you get the error you're getting. Here's some code that demonstrates the change in definition:
class Foo
end
describe "" do
it "" do
Foo.any_instance.stub(:bar)
foo1 = Foo.new
foo2 = Foo.new
print_bars = -> (context) {puts "#{context}, foo1#bar is #{foo1.method(:bar)}, foo2#bar is #{foo2.method(:bar)}"}
print_bars['before call']
foo1.bar
print_bars['after call']
end
end
which produces the following output:
before call, foo1#bar is #<Method: Foo#bar>, foo2#bar is #<Method: Foo#bar>
after call, foo1#bar is #<Method: #<Foo:0x007fc0c3842ef8>.bar>, foo2#bar is #<Method: Foo#bar>
I reported this an issue on RSpec's github site and got this acknowledgement/response.
You can use the following alternative approach, which depends on the recently introduced expect_any_instance_of method.
class Roll
def initialize
install if !installed?
end
def install; puts 'install'; end
end
describe Roll do
before do
class RollTestClass < Roll; end
end
let(:roll_class) { RollTestClass }
let(:roll) { RollTestClass.new }
context 'when installed is true' do
before do
roll_class.any_instance.stub(:installed?).and_return(true)
end
it 'should not call install' do
expect_any_instance_of(roll_class).to_not receive(:install)
roll
end
end
context 'when installed is false' do
before do
roll_class.any_instance.stub(:installed?).and_return(false)
end
it 'should call install' do
expect_any_instance_of(roll_class).to receive(:install)
roll
end
end
end

How do I write functional test for this

I am pretty new to rspec. How do I write functional test for following piece of code.
class FooController < ApplicationController
 def new
#title = "Log in to Mint"
#msg = session[:msg]
session[:msg] = nil
end
end
 
How about something like this:
describe FooController do
describe "GET new" do
it "assigns 'Log in to Mint' to #title" do
get :new
assigns(:title).should == "Log in to Mint"
end
it "assigns message session to #msg" do
session[:msg] = "a message"
get :new
assigns(:msg).should == "a message"
end
it "sets message session to nil" do
get :new
session[:msg].should be_nil
end
end
end
See also: Rspec: testing assignment of instance variable

How can I clear class variables between rspec tests in ruby

I have the following class:
I want to ensure the class url is only set once for all instances.
class DataFactory
##url = nil
def initialize()
begin
if ##url.nil?
Rails.logger.debug "Setting url"
##url = MY_CONFIG["my value"]
end
rescue Exception
raise DataFactoryError, "Error!"
end
end
end
I have two tests:
it "should log a message" do
APP_CONFIG = {"my value" => "test"}
Rails.stub(:logger).and_return(logger_mock)
logger_mock.should_receive(:debug).with "Setting url"
t = DataFactory.new
t = nil
end
it "should throw an exception" do
APP_CONFIG = nil
expect {
DataFactory.new
}.to raise_error(DataFactoryError, /Error!/)
end
The problem is the second test never throws an exception as the ##url class variable is still set from the first test when the second test runs.
Even though I have se the instance to nil at the end of the first test garbage collection has not cleared the memory before the second test runs:
Any ideas would be great!
I did hear you could possibly use Class.new but I am not sure how to go about this.
describe DataFactory
before(:each) { DataFactory.class_variable_set :##url, nil }
...
end
Here is an alternative to the accepted answer, which while wouldn't solve your particular example, I'm hoping it might help a few people with a question in the same vein. If the class in question doesn't specify a default value, and remains undefined until set, this seems to work:
describe DataFactory
before(:each) do
DataFactory.remove_class_variable :##url if DataFactory.class_variable_defined? :##url
end
...
end
Works for me with a class with something more like:
def initialize
##url ||= MY_CONFIG["my value"]
...
end

Resources