ChefSpec: How to mock node.chef_environment? - chefspec

Does anyone know how to mock the node attribute "node.chef_environment" in chefspec?
template 'my_script.sh' do
source 'my_script.erb'
variables(
environment: node.chef_environment
)
end
Unfortunately, mocking via default_attributes does not work.
describe "my_test" do
context "create template file" do
default_attributes['chef_environment']= 'my_env'
it { is_expected.to render_file('my_script.sh').with_content('env=my_env')}
end
end

You have to mock out Chef's Environment class and construct a runner with it.
cached(:chef_run) do
ChefSpec::SoloRunner.new do |node|
env = Chef::Environment.new
env.name 'my-fake-env'
allow(node).to receive(:chef_environment).and_return(env.name)
allow(Chef::Environment).to receive(:load).and_return(env)
end.converge(described_recipe)
end
Here is a good discussion on the topic.

Related

How do I test this particular method?

I have the following method that is responsible for requesting a URL and returning it's Nokogiri::HTML document. This method checks if a proxy is defined and if it does, it will call OpenURI's open with or without the proxy options.
Implementation
require 'open-uri'
require 'nokogiri'
class MyClass
attr_accessor :proxy
# ....
def self.page_content(url)
if MyClass.proxy
proxy_uri = URI.parse(MyClass.proxy)
Nokogiri::HTML(open(url, :proxy => proxy_uri)) # open provided by OpenURI
else
Nokogiri::HTML(open(url)) # open provided by OpenURI
end
end
end
I have no idea how I should write tests that prove the following:
When a proxy is defined the request OpenURI makes actually uses the proxy info
When a proxy isn't defined, a regular non-proxy connection is made
Here's what I came up with as a start for the tests.
describe MyClass, :vcr do
describe '.proxy' do
it { should respond_to(:proxy) }
end
describe '.page_content' do
let(:url) { "https://google.com/" }
let(:page_content) { subject.page_content(url) }
it 'returns a Nokogiri::HTML::Document' do
page_content.should be_a(Nokogiri::HTML::Document)
end
# How do i test this method actually uses a proxy when it's set vs not set?
context 'when using a proxy' do
# ???
xit 'should set open-uri proxy properties' do
end
end
context 'when not using a proxy' do
# ???
xit 'should not set open-uri proxy properties' do
end
end
end
end
First of all, you need to arrange for the proxy method to return a proxy in one test case and not in the other. If there is a "setter" method for proxy, you can use that, otherwise you can stub the proxy method.
Then, at a minimum, you want to set an expectation on open that it will be called with or without the :proxy option, depending on which test it is. Beyond that, you have the choice of whether to stub and set expectations for the various other calls involved in the method, including URI.parse and Nokogiri::HTML.
See https://github.com/rspec/rspec-mocks for information on establishing your test doubles and setting expectations. Note in particular the and_call_original option if you want to use a partial stubbing approach.
Update: Here's some code to get you started. This works for the non-proxy method. I've left the proxy case for you. Note also that this uses the "partial stubbing" approach, where you still end up calling the external gems.
require 'spec_helper'
describe MyClass do
describe '.proxy' do # NOTE: This test succeeds because of attr_accessor, but you're calling a MyClass.proxy (a class method) within your page_content method
it { should respond_to(:proxy) }
end
describe '.page_content' do
let(:url) { "https://google.com/" }
let(:page_content) { MyClass.page_content(url) } # NOTE: Changed to invoke class method
context 'when not using a proxy' do
before {allow(MyClass).to receive(:proxy).and_return(false)} # Stubbed for no-proxy case
it 'returns a Nokogiri::HTML::Document' do
page_content.should be_a(Nokogiri::HTML::Document)
end
it 'should not set open-uri proxy properties' do
expect(MyClass).to receive(:open).with(url).and_call_original # Stubbing open is tricky, see note afterwards
page_content
end
end
# How do i test this method actually uses a proxy when it's set vs not set?
context 'when using a proxy' do
# ???
xit 'should set open-uri proxy properties' do
end
end
end
end
Stubbing of open is tricky. See How to rspec mock open-uri? for an explanation.

How to override setup values, like RSpec `let`, in shoulda contexts?

Here's a nice technique I use with RSpec that I would also like to use in projects that use Shoulda and Shoulda-context. But I don't know if it's possible. Is there a way to do this?
What I want: define a setup (before) block in an outer context that references a let clause in a nested context. That way, inner contexts can configure values that are referenced in the outer setup And the setup can still be DRY across inner contexts.
RSpec example (this example is simple--please assume my real-life examples have a lot more code in the before block that I don't want to duplicate):
describe Thing do
before do
# Notice that `user` isn't defined here--it's defined in `let` blocks
# in nested contexts below.
login_as user
# Assume there's lots more code here that I would like to keep
# DRY across contexts.
end
context "when logged in as an admin" do
# THIS IS THE MAGIC RIGHT HERE:
let(:user) { Factory(:user, role: "admin") }
it "should ..." ...
end
context "when logged in as a normal user" do
# THIS IS THE MAGIC RIGHT HERE:
let(:user) { Factory(:user) }
it "should ..." ...
end
end
To summarize: how can I do this with shoulda-context and Test::Unit?
Some things I have already tried that didn't work:
def to redefine a method in each subcontext.
before_should in each subcontext.
I've found helper methods inside the test class useful for pulling out code repeated between tests. Like this:
class MyTest < TestCase
context "when logged in as an admin" do
setup do
do_login Factory(:user, role: "admin")
end
should "..." do
...#user...
end
end
context "when logged in as an admin" do
setup do
do_login Factory(:user)
end
should "..." do
...#user...
end
end
def do_login(user)
login_as user
#user = user
# lots more setup code here...
end
end

Passing an object as subject to rspec

I am running rspec tests on a catalog object from within a Ruby app, using Rspec::Core::Runner::run:
File.open('/tmp/catalog', 'w') do |out|
YAML.dump(catalog, out)
end
...
unless RSpec::Core::Runner::run(spec_dirs, $stderr, out) == 0
raise Puppet::Error, "Unit tests failed:\n#{out.string}"
end
(The full code can be found at https://github.com/camptocamp/puppet-spec/blob/master/lib/puppet/indirector/catalog/rest_spec.rb)
In order to pass the object I want to test, I dump it as YAML to a file (currently /tmp/catalog) and load it as subject in my tests:
describe 'notrun' do
subject { YAML.load_file('/tmp/catalog') }
it { should contain_package('ppet') }
end
Is there a way I could pass the catalog object as subject to my tests without dumping it to a file?
I am not very clear as to what exactly you are trying to achieve but from my understanding I feel that using a before(:each) hook might be of use to you. You can define variables in this block that are available to all the stories in that scope.
Here is an example:
require "rspec/expectations"
class Thing
def widgets
#widgets ||= []
end
end
describe Thing do
before(:each) do
#thing = Thing.new
end
describe "initialized in before(:each)" do
it "has 0 widgets" do
# #thing is available here
#thing.should have(0).widgets
end
it "can get accept new widgets" do
#thing.widgets << Object.new
end
it "does not share state across examples" do
#thing.should have(0).widgets
end
end
end
You can find more details at:
https://www.relishapp.com/rspec/rspec-core/v/2-2/docs/hooks/before-and-after-hooks#define-before(:each)-block

Automatically share context in RSpec

I want to share a memoized method between my specs. So I tried to use shared context like this
RSpec.configure do |spec|
spec.shared_context :specs do
let(:response) { request.execute! }
end
end
describe 'something' do
include_context :specs
end
It works ok. But I have about 60 spec files, so I'm forced to explicitly include context in each of them. Is there an way to automatically include shared context (or at least let definition) for all example groups in spec_helper.rb?
Something like this
RSpec.configure do |spec|
spec.include_context :specs
end
You can set up global before hooks using RSpec.configure via configure-class-methods and Configuration:
RSpec.configure {|c| c.before(:all) { do_stuff }}
let is not supported in RSpec.configure, but you can set up a global let by including it in a SharedContext module and including that module using config.before:
module MyLetDeclarations
extend RSpec::Core::SharedContext
let(:foo) { Foo.new }
end
RSpec.configure { |c| c.include MyLetDeclarations }
In RSpec 3+, this can be achieved as follows - based on Jeremy Peterson's answer.
# spec/supprt/users.rb
module SpecUsers
extend RSpec::SharedContext
let(:admin_user) do
create(:user, email: 'admin#example.org')
end
end
RSpec.configure do |config|
config.include SpecUsers
end
You can do it almost like that: there's a mechanism for including a module, and module inclusion has its own callback mechanism.
Suppose for example that we have a disconnected shared context that we want to use to run all our model specs without a database connection.
shared_context "disconnected" do
before :all do
ActiveRecord::Base.establish_connection(adapter: :nulldb)
end
after :all do
ActiveRecord::Base.establish_connection(:test)
end
end
You can now create a module that will include that context on inclusion.
module Disconnected
def self.included(scope)
scope.include_context "disconnected"
end
end
Finally, you can include that module into all specs in the normal manner (I've demonstrated doing it only for models, just to show that you can), which is almost exactly what you asked for.
RSpec.configure do |config|
config.include Disconnected, type: :model
end
That works with rspec-core 2.13.0 and rspec-rails 2.13.0.
Another way to go is to automatically share examples via metadata. So:
shared_context 'a shared context', a: :b do
let(:foo) { 'bar' }
end
describe 'an example group', a: :b do
# I have access to 'foo' variable
end
The most common way I use it is in rspec-rails, with some shared context depending on the example group type. So if you have config.infer_spec_type_from_file_location!, you can simply do:
shared_context 'a shared context', type: :controller do
let(:foo) { 'bar' }
end
describe SomeController do
# I have access to 'foo' variable
end
Also if you need ability to use shared data in before blocks inside specs, as me, try to include this (if its Rails project):
module SettingsHelper
extend ActiveSupport::Concern
included do
attr_reader :default_headers
before :all do
#default_headers = Hash[
'HTTP_HOST' => 'test.lvh.me'
]
end
after :all do
#default_headers = nil
end
end
end
RSpec.configure do |config|
config.include SettingsHelper
end
Or try something similar, look at #threedaymonk answer.

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