Please forgive my gherkin
Given I have a module called Hello
And it has a method called world and a class called World
When I include the module in an RSpec spec
And I call the method world
Then it returns "hello world from method"
But when I create a World class
Then I get NameError: uninitialized constant World
module Hello
def world
p "hello world from method"
end
class World
def initialize
p "hello world from class"
end
end
end
describe "hello world" do
include Hello
it "call method" do
world
end
it "call class" do
World.new
end
end
Forgive my Gherkin, and unless you intended something else (as that's not clear)
When I create a Hello::World class
Then I get an object of kind Hello::World
it "should not catch fire on instantiation" do
Hello::World.new.should_not be_nil
end
Related
Im writing a test for this service.
def run
sort_offers(product).each do |product_code|
......
offer.update(poduct_params)
Importer::Partner.get_details(product_code).new
end
end
It's calling a service which in some cases will override the values that were saved when running offer.update(product_prams). How would I go about skipping the service call within my test?
Here is the example of my test
context 'is valid' do
.... .....
before do
Importer::ProductCodes(product).run
end
it ......
end
I would stub Importer::Partner.get_details to return a double that responds to new:
context 'is valid' do
before do
allow(Importer::Partner).to receive(:get_details).and_return(double(new: nil))
end
# it ...
end
Depending on your needs you might want to add an expectation that the mock was called with the correct parameters and that new was actually called on the mock too:
context 'is valid' do
let(:mock) { double(new: nil) }
before do
allow(Importer::Partner).to receive(:get_details).and_return(double(new: nil))
end
it "calls the service" do
an_instance.run
expect(Importer::Partner).to have_received(:get_details).with(
foo: 'bar' # the arguments you would expect
)
expect(mock).to have_received(:new)
end
end
RSpec has a very capable stubbing and mocking library built in (rspec mocks).
require 'spec_helper'
module Importer
class Partner
def self.get_details(product_code)
"original return value"
end
end
end
class FooService
def self.run
Importer::Partner.get_details('bar')
end
end
RSpec.describe FooService do
let(:partner_double) { class_double("Importer::Partner") }
before do
stub_const("Importer::Partner", partner_double)
allow(partner_double).to receive(:get_details).and_return 'our mocked value'
end
it "creates a double for the dependency" do
expect(FooService.run).to eq 'our mocked value'
end
end
class_double creates a double for the class and you can set the return values by using .expect and .allow and the mocking interface. This is quite useful since you can stub out the new or intialize methods to return a double or spy.
stub_constant will reset the constant to its previous value when the spec is done.
That said you can avoid the use of stub_constant by using constructor injection in your services:
class PhotoImportService
attr_accessor :client, :username
def initialize(username, api_client: nil)
#username = username
#client = api_client || APIClient.new(ENV.fetch('API_KEY'))
end
def run
client.get_photos(username)
end
end
I have a helper method defined in my RSpec spec which creates an instance of the class Dog. But the spec is not able to recognize the method call dog.good_dog.
helpers_demo_spec.rb
class Dog
attr_reader :good_dog, :has_been_walked
def initialize(good_or_not)
#good_dog = good_or_not
#has_been_walked = false
end
def walk_dog
#has_been_walked = true
end
end
describe Dog do
# helper method
def create_and_walk_dog(good_or_bad)
Dog.new(good_or_bad).walk_dog
end
it 'should be able to create and walk a good dog' do
dog = create_and_walk_dog(true)
expect(dog.good_dog).to be true
expect(dog.has_been_walked).to be true
end
end
Error Log:
C:\nital\my-data\my-sample-apps\Rails-Samples\rspec-samples\lib>rspec spec\helpers_demo_spec.rb
F
Failures:
1) Dog should be able to create and walk a good dog
Failure/Error: expect(dog.good_dog).to be true
NoMethodError:
undefined method `good_dog' for true:TrueClass
# ./spec/helpers_demo_spec.rb:26:in `block (2 levels) in <top (required)>'
Finished in 0.001 seconds (files took 0.33463 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/helpers_demo_spec.rb:24 # Dog should be able to create and walk a good dog
The RSpec way of achieving what you want to do is to use blocks such as subject, let, before, after, etc.
describe Dog do
context 'good dog' do
subject { Dog.new(true) }
before(:each) do
subject.walk
end
it 'should be a good dog' do
expect(subject.good_dog).to be true
end
it 'should be a walked dog' do
expect(subject.has_been_walked).to be true
end
end
end
Your helper method returns either a TrueClass or a FalseClass while your spec expects a Dog instance. Your helper methods needs to return a Dog instance. You should update your code to look like this:
def create_and_walk_dog(good_or_bad)
dog = Dog.new(good_or_bad)
dog.walk_dog
dog
end
def create_and_walk_dog(good_or_bad)
Dog.new(good_or_bad).walk_dog
end
You don't want walk_dog to be called here. It returns true, which gives you your error.
def create_and_walk_dog(good_or_bad)
Dog.new(good_or_bad)
end
I have a helper method that uses 'request' to determine the URL. However, rspec can't seem to find request. I thought request was available to all front-facing tests?
How can I account for the request method in my spec?
Helper Spec
require 'spec_helper'
describe ApplicationHelper do
describe "full_title" do
it "should include the page title" do
expect(full_title("help")).to include('help')
end
end
end
Helper methods
def full_title(page_title)
if staging? # causing the issue
base_title = "Staging"
else
base_title = "Company Name"
end
if page_title.empty?
"#{base_title} | Tag line "
else
"#{base_title} | #{page_title} "
end
end
def staging? # the request here seems to be the problem
request.original_url.include? "staging"
end
Rspec error
Failure/Error: expect(full_title("help")).to include('help')
NameError:
undefined local variable or method `request' for #<RSpec::ExampleGroups::ApplicationHelper_2::FullTitle:0x00000106260078>
Thanks in advance.
First off: request is only available in the controller tests (and even then only in the request specs I think), helper tests are really basic and isolated. Which is good. Your helper code should be really minimal and normally only work on the input it receives.
However this is pretty easily solvable by using stubbing.
So write something like
#note, OP needed to replace 'helper' with 'self'for Rails 4.0.0 and Rspec 3.0
require 'rails_helper'
describe ApplicationHelper do
describe "full_title" do
context "in staging" do
it "should include the page title" do
helper.should_receive(:staging?).and_return(true)
expect(full_title("help")).to include('help')
end
end
context "not in staging" do
it "should include the page title" do
helper.should_receive(:staging?).and_return(false)
expect(full_title("help")).to include('help')
end
end
end
end
Which is imho a very clear, and then you write separate tests for your staging? method:
describe "staging?" do
context "when in staging" do
it "returns true" do
helper.stub(:request) { OpenStruct.new(original_url: 'staging') }
expect( helper.staging? ).to be true
end
end
context "when not in staging" do
it "returns false" do
helper.stub(:request) { OpenStruct.new(original_url: 'development') }
expect(helper.staging?).to be false
end
end
end
end
Some small remarks: ruby default indentation is 2 spaces.
Secondly, your function now literally says return true if true, ideally it should be written like
def staging?
request.original_url.include? "staging"
end
Trying to learn Ruby and below is a module I have created in order to test IO Ruby features. When I run the following tests:
subject{TestGem.new}
it 'should be_a Module' do
subject.is_a Module
end
it 'creates a config file' do
subject.init_config
File.open("program.config", "r") { |io| io.readline }.should eq "default_file.txt"
end
I get this error for both:
Failure/Error: subject{TestGem.new}
NoMethodError:
undefined method `new' for TestGem:Module
Here is the Module I am testing. Any help/suggestions would greatly appreciated :)
$LOAD_PATH.unshift File.expand_path("../test_gem", __FILE__)
require 'version'
require 'hello'
module TestGem
#default_file_name
#supported_types
def initialize
default_file_name = 'default_file.txt'
supported_types = ['txt', 'pdf']
end
puts "module TestGem defined"
def self.init_config
File.open('program.config', "w") do |f|
f.write(yaml(#default_file_name))
f.write(yaml(#supported_types))
end
end
class MyFile
def self.first(filename)
File.open(filename, "r") {|f| f.readline}
end
def self.last(filename)
File.open(filename, "r")[-1]
end
end
class MyError < StandardError
puts "Standard Error"
end
end
Short answer: You can't instantiate modules objects
module A
end
class B
end
B.methods - A.methods #=> [:new, :superclass, :allocate]
To test a module you can include it in a object like this
object = Object.new
object.extend(TestGem)
Or you can create some example class if your module depends on some class behavior.
I have the following in my application_helper_spec.rb
shared_examples_for "some shared process" do
it "displays the value defined by Let" do
puts object.inspect # This should output the value defined by Let
end
end
describe ApplicationHelper do
describe "a_helper_methond" do
describe "some behaviour" do
let(:object) { "foo" }
puts object.inspect # This should output the value defined by Let
it_should_behave_like "some shared process"
end
end
end
However, when I run this I get two errors on both puts:
undefined local variable or method `object' for #<Class:0x007f951a3a7b20> (NameError)
Why? I have the exact same code in my model and request specs at it runs fine, but in the helper specs it doesn't.
The let call defines a method in the context your specs will be executed. Your puts statement however is outside that scope. You need to wrap it inside an it block.
it 'print debug output' do
puts object.inspect
end
I also further improved this by putting the Let command inside the it_behaves_like block