I'm trying to make a test double for a class instance with RSpec. Say I have a test that only accepts a File object as an argument.
Great, now how do I make a double so I don't have to pass in an actual file with all of my specs?
let(:file) { double(File) }
raise "NOT A FILE" unless file.is_a? File
# => RuntimeError: NOT A FILE
I've also tried this:
let(:file) { instance_double(File) }
raise "NOT A FILE" unless file.is_a? File
# => RuntimeError: NOT A FILE
And this (which is expecting an actual file):
let(:file) { object_double(File.new) }
# => ArgumentError: wrong number of arguments
What am I doing wrong?
You can just stub the is_a? call.
file = instance_double(File)
allow(file).to receive(:is_a?).with(File).and_return(true)
It is true you can stub #is_a?. But I would say, instead of trying to figure out what object it is, you could try to figure if it knows how to do the thing you need to do.
For instance, instead of received_file.is_a?(File), you could do received_file.respond_to?(:write).
This way, you can pass a Tempfile, or a File, or even an RSpec InstanceDouble(File). If the class knows how to #write then, we trust it.
Related
I am getting template error due to backend.key=<%= node['key']%> used in source key.properties.erb doesn’t have a value while running shellout.
Error : Chef::Mixin::Template::TemplateError - undefined method `[]' for nil:NilClass
I have a ruby block to get the output of the file cat /tmp/key.txt and assigning as a node value.
Ruby block :
ruby_block "Get_key" do
block do
#tricky way to load this Chef::Mixin::ShellOut utilities
Chef::Resource::RubyBlock.send(:include, Chef::Mixin::ShellOut)
command = 'cat /tmp/key.txt'
command_out = shell_out(command)
node.set['key'] = command_out.stdout
end
action :create
end
Erb :
backend.key=<%= node['key']%>
There is no need to shell_out to read the contents of a file. Try this instead:
ruby_block "Get_key" do
only_if { node['key'] == "" }
block do
node.set['key'] = File.read('/tmp/key.txt')
end
end
But I think your actual problem is somewhere else. The error message indicates that node is nil within your template, which is pretty unusual.
So either I am blind and you really have some typo in the posted template line, or you simplified your code example in such a way that it hides your error. I assume your real template looks more like
backend.key=<%= node['foo']['key']%>
and foo not being an array. Check that.
Please don't use this pattern. It's slow and puts extra data in your node object which takes up space and RAM and makes you search index sad. What you want is this:
template "whatever" do
# Other stuff ...
variables my_file: lazy { IO.read('/tmp/key.txt') }
end
That will delay the read until converge time.
I have the following Ruby code:
def report_deviation(departure)
deviation = departure.fetch('Dev')
trip = departure.fetch('Trip')
run_id = trip.fetch('RunId')
headsign = trip.fetch('InternetServiceDesc')
timestamp = Time.now.strftime '%l:%M %P'
FileUtils.mkdir 'log' unless File.directory? 'log'
File.open DAILY_LOG_FILE, 'a' do |file|
file.puts "#{timestamp}, #{name}: Run #{run_id} (#{headsign}), deviation #{deviation}"
end
end
Tested by the following RSpec code:
describe 'report_deviation' do
let(:departure) { double }
let(:trip) { double }
let(:file) { double }
it 'appends to a log file with the correct entry format' do
expect(departure).to receive(:fetch).with('Trip').and_return trip
expect(departure).to receive(:fetch).with('Dev').and_return 'DEVIATION'
expect(trip).to receive(:fetch).with('RunId')
.and_return 'RUN'
expect(trip).to receive(:fetch).with('InternetServiceDesc')
.and_return 'HEADSIGN'
stub_const 'DeviationValidator::DAILY_LOG_FILE', :log_file
expect(File).to receive(:open).with(:log_file, 'a').and_yield file
timestamp = '12:00 pm: Run RUN (HEADSIGN), deviation DEVIATION'
expect(file).to receive(:puts).with timestamp
Timecop.freeze(Time.new 2017, 7, 31, 12) { report_deviation(departure) }
end
end
But when I run I receive the failure message:
`name` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).
The word name isn't written anywhere in here, and if I remove the final line of the test (which invokes the actual code) I get the test failures I would expect for unsatisfied exceptions. I normally would boil my code down to the pieces that are causing the error, but I have no idea what's causing the error.
For what it's worth, the specific line number mentioned in the backtrace is the file.puts within the File.open block - but I don't understand why that should cause a failure. I've set up test doubles such that those objects are nothing special - File receives open and yields file, whose only job is to listen for receiving puts with the string I expect. So what piece of code is calling what happens to be a keyword RSpec method name?
The problem is from rspec gem, if you are using Rails 6 you need to use gem 'rspec-rails', '~> 4.1.0'
name is not a keyword RSpec method, it's a method that report_deviation is trying to call
file.puts "#{timestamp}, #{name}: Run #{run_id} (#{headsign}), deviation #{deviation}"
but the method is not defined.
You need to define the name method in the class where report_deviation is defined. Or, if report_deviation is defined and used in the spec file, add a simple variable called name:
describe 'report_deviation' do
let(:departure) { double }
let(:trip) { double }
let(:file) { double }
let(:name) { "simple name" }
...
`name` is not available from within an example (e.g. an `it` block) [...]
I had a similar problem today. The final solution for the issue for now with a monkeypatch to go back to using method_name.
Create config/initializers/monkeypatches.rb file and fill inside with the following lines.
# config/initializers/monkeypatches.rb
#
# This fixes what seems to be a bug introduced by
# https://github.com/rails/rails/pull/37770
# "Modify ActiveRecord::TestFixtures to not rely on AS::TestCase:"
#
module ActiveRecord::TestFixtures
def run_in_transaction?
use_transactional_tests &&
!self.class.uses_transaction?(method_name) # this monkeypatch changes `name` to `method_name`
end
end
Credits: https://github.com/graphql-devise/graphql_devise/issues/42
This may be a simple matter of mocking a resource, but...
class myclass (
String stringParam,
Integer intParam,
File fileParam
) {
# do some things
$path = fileParam['title']
$mode = fileParam['mode']
# do some more things
}
Now I want to write an rspec-puppet test for this class. How do I either create or mock a File resource and get it into the catalog that rspec-puppet uses, so that I can reference it?
The answers to this and this got me partway there, but everything I've tried has led to myClass complaining that it's being passed a string instead of a file reference.
...
let(:params) {{
:stringParam => 'Here is my string',
:intParam => 238,
:fileParam => *??????,*
}}
There isn't really much support in rspec-puppet for this, as a class test parameters list is generated from the :params assuming only strings (or nested hashes/arrays etc. containing strings) or a couple of permitted symbol values used almost literally, :undef and :default. It doesn't have a way of passing in resource references.
A workaround exists that lets you put literal content into a manifest though, by passing an object that responds to the inspect method. For example, in your spec_helper.rb:
class ResourceReference
def initialize(ref)
#ref = ref
end
def inspect
#ref
end
end
And then in your spec:
let(:params) {{
:stringParam => 'Here is my string',
:intParam => 238,
:fileParam => ResourceReference.new("File[/etc/foo]"),
}}
rspec-puppet will call the inspect method on the ResourceReference object which returns the string you've passed in. This should be placed in the manifest unchanged.
(This was originally used as a workaround for undef, which can now be passed as :undef.)
As an aside, you can set let(:pre_condition) { "..." } to add literal content to the test manifest before the generated class { ... }, but I don't think there's a way to use that here.
I'd strongly recommend filing a feature request against rspec-puppet.
I have a question on how to use rspec to mock a file input. I have a following code for the class, but not exactly know a why to mock a file input. filepath is /path/to/the/file
I did my search on Google and usually turns out to be loading the actual file instead of mocking, but I'm actually looking the opposite where only mock, but not using the actual file.
module Service
class Signing
def initialize(filepath)
#config = YAML.load_file(filepath)
raise "Missing config file." if #config.nil?
end
def sign() …
end
private
def which() …
end
end
end
Is it possible to use EOF delimiter for this file input mocking?
file = <<EOF
A_NAME: ABC
A_ALIAS: my_alias
EOF
You could stub out YAML.load_file and return parsed YAML from your text, like this:
yaml_text = <<-EOF
A_NAME: ABC
A_ALIAS: my_alias
EOF
yaml = YAML.load(yaml_text)
filepath = "bogus_filename.yml"
YAML.stub(:load_file).with(filepath).and_return(yaml)
This doesn't quite stub out the file load itself, but to do that you'd have to make assumptions about what YAML.load_file does under the covers, and that's not a good idea. Since it's safe to assume that the YAML implementation is already tested, you can use the code above to replace the entire call with your parsed-from-text fixture.
If you want to test that the correct filename is passed to load_file, replace the stub with an expectation:
YAML.should_receive(:load_file).with(filepath).and_return(yaml)
If the idea is to put an expectation on something, I don't see much benefit on this approach of calling YAML.load to fake the return. YAML.load_file actually returns a hash, so instead of doing all that my suggestion would be to simply return a hash:
parsed_yaml = {
"somekey" => {
"someotherkey" => "abc"
}
}
YAML.should_receive(:load_file).with(filepath).and_return(parsed_yaml)
As this is supposed to be a unit test and not an integration test, I think this would make more sense.
I am writing a spec for an object (Sample) that calls another object's method (IO#delete) a number of time.
I want to isolate the tests of this method, however when I do this:
class Sample
def delete_them
io.delete "file1"
io.delete "folder1"
end
end
describe Sample do
let(:io) { stub.as_null_object }
subject { Sample.new.tap { |s| s.stub(:io).and_return(io) }}
it "deletes file1" do
io.should_receive(:delete).with("file1")
subject.delete_them
end
it "deletes folder1" do
io.should_receive(:delete).with("folder1")
subject.delete_them
end
end
If I call multiple methods it's not a problem because I am using the null object pattern. However, in this case when I execute the second test, it complains:
1) Sample instance methods#delete_them deletes folder1
Failure/Error: io.should_receive(:delete).with("folder1")
Stub received :delete with unexpected arguments
expected: ("folder1")
got: ("file1")
Is there a way to indicate that all the calls must be ignored except the one I am trying to make sure is being done?
This code compiles fine. It was another issue that was causing my problem.