Shoulda: How would I use an instance variable outside of a setup or should block? - ruby

I'm trying to do something like the following:
#special_attributes = Model.new.methods.select # a special subset
#special_attributes.each do |attribute|
context "A model with #{attribute}" do
setup do
#model = Model.new
end
should "respond to it by name" do
assert_respond_to #model, attribute
end
end
end
However, #special_attributes is out of scope when running the unit tests, leaving me with a nil object on line 2. I can't figure out where/how to define it to bring it in scope. Any thoughts?

Got it (I think). Shoulda is executing the block in the context of Shoulda::Context. In the above case, #special_attributes is an instance variable of my test class, not Shoulda::Context. To fix this, instead of using instance variables, just use local variables in the context block.
So, for example:
context "Model's" do
model = Model.new
special_attributes = model.methods.select # a special subset
special_attributes.each do |attribute|
context "attribute #{attribute}" do
setup do
#model = model
end
should "should have a special characteristic"
assert_respond_to #model, attribute
...
end
end
end
end

Related

Rspec how to mock function call through instance method

I am trying to test a method which uses instance variable to call featureEnabled method. I am trying to write a rspec unit test for this. I am using below setup in the allow statement. Not sure how else to do this
exception: => #<NoMethodError: undefined method `feature_enabled?' for nil:NilClass>
Api.controllers :Customers do
#domain = current_account.domain
def main
t1 = #domain.featureEnabled?("showPages")
blah
Test:
RSpec.describe ApiHelpers do
describe "#find_matching_lists" do
let(:domain) { Domain.new }
it "madarchod3" do
allow(domain).to receive(:featureEnabled?).with("showPages").and_return(true)
end
end
Variables defined in a block are local to this block, they do not exist outside of it.
The problem you're having is that the domain variable you create with let is only visible inside the block passed to describe. Your test must be defined in that describe block for it to access this variable, ie this should work:
describe "#main" do
let(:domain) { Domain.new }
it "check if feature enabled" do
allow(domain).to receive(:featureEnabled?).with("showPages").and_return(true)
end
end

rspec way for passing variable between multiple contexts

I was wondering what would be the best way to pass variable between multiple contexts (or multiple its) in rspec but without using global variables?
For example, I have this:
describe "My test" do
let(:myvar) { #myvar = 0 }
context "First test pass" do
it "passes" do
myvar = 20
expect(myvar).to eq(20)
end
end
context "Second test pass" do
it "passes" do
expect(myvar).to eq(20)
end
end
end
Now, obviously, this will not work with let because with new context, myvar variable will be back on initial state which is = 0.
I would need mechanism to "cache state" between two contexts which would in turn give me value of myvar = 20 in second context
Any opinions, suggestions and improvements are welcome.
Thanks
Another simple way, would be to define a 'local variable' in describe context.
the 'local variable' would live throughout the describe, and any changes during run time would effect it, and so change it.
For example
describe 'tests' do
context 'Sharing a variable across tests' do
var = 1
puts var
it "it one. var = #{var}" do
var = var*2
puts var
end
it "it two" do
puts var
end
end
end
Output
1
2
1
What happens is not what you think happens.
What you want to happen break "unit testing" as a methodology.
Let me explain #2 first - unit testing test cases should be able to work in isolation, which means that they should work when run together, when run apart, and in any order... so much so that some unit testing frameworks (like the default one in elixir) run test cases in parallel...
As for #1 - when you write myvar = 20 you are not assigning a value to let(:myvar) { #myvar = 0 }, you simply create a local variable, which will override all calls to myvar within the method, but will not be available outside the method (myvar will return 0).
Even if you would have set #myvar = 20 (unless you do it before you call myvar for the first time) instead, myvar would still return 0, since the let function is using a memento pattern, which means it is called once, and subsequent calls return the value originally returned (in this case 0):
puts myvar
#myvar = 20
puts myvar
# => 0
# => 0
I just ran into this same problem. How I solved it was by using factory_girl gem.
Here's the basics:
create a factory:
require 'factory_girl'
require 'faker' # you can use faker, if you want to use the factory to generate fake data
FactoryGirl.define do
factory :generate_data, class: MyModule::MyClass do
key 100 # doesn't matter what you put here, it's just a placeholder for now
another_key 'value pair'
end
end
Now after you made the factory you need to make a Model that looks like this:
Module MyModule
class MyClass
#for every key you create in your factory you must have a corresponding attribute accessor in the model.
attr_accessor :key, :another_key
#you can also place methods here to call from your spec test, if you wish
# def self.test
#some test
# end
end
end
Now going back to your example you can do something like this:
describe "My test" do
let(:myvar) { #myvar }
context "First test pass" do
it "passes" do
#myvar.key = 20 #when you do this you set it now from 100 to 20
expect(#myvar.key).to eq(20)
end
end
context "Second test pass" do
it "passes" do
expect(#myvar.key).to eq(20) #it should still be 20 unless you overwrite that variable
end
end
end
As stated by others, not proper way of unit testing. But, how should we know if you're unit testing or not. So, I won't judge.
Anyways, good luck let us know, if you got some other solution!

DRY within a Chef recipe

What's the best way to do a little DRY within a chef recipe? I.e. just break out little bits of the Ruby code, so I'm not copying pasting it over and over again.
The following fails of course, with:
NoMethodError: undefined method `connect_root' for Chef::Resource::RubyBlock
I may have multiple ruby_blocks in one recipe, as they do different things and need to have different not_if blocks to be truley idempotent.
def connect_root(root_password)
m = Mysql.new("localhost", "root", root_password)
begin
yield m
ensure
m.close
end
end
ruby_block "set readonly" do
block do
connect_root node[:mysql][:server_root_password] do |connection|
command = 'SET GLOBAL read_only = ON'
Chef::Log.info "#{command}"
connection.query(command)
end
end
not_if do
ro = nil
connect_root node[:mysql][:server_root_password] do |connection|
connection.query("SELECT ##read_only as ro") {|r| r.each_hash {|h|
ro = h['ro']
} }
end
ro
end
end
As you already figured out, you cannot define functions in recipes. For that libraries are provided. You should create a file (e.g. mysql_helper.rb ) inside libraries folder in your cookbook with the following:
module MysqlHelper
def self.connect_root( root_password )
m = Mysql.new("localhost", "root", root_password)
begin
yield m
ensure
m.close
end
end
end
It must be a module, not a class. Notice also we define it as static (using self.method_name). Then you will be able to use functions defined in this module in your recipes using module name with method name:
MysqlHelper.connect_root node[:mysql][:server_root_password] do |connection|
[...]
end
For the record, I just created a library with the following. But that seems overkill for DRY within one file. I also couldn't figure out how to get any other namespace for the module to use, to work.
class Chef
class Resource
def connect_root(root_password)
...

How do you make ruby variables and methods in scope using Thor Templates?

I'm trying to use the Thor::Actions template method to generate some C++ test file templates, but erb keeps telling me that I have undefined variables and methods.
Here's the calling code:
def test (name, dir)
template "tasks/templates/new_test_file", "src/#{dir}/test/#{name}Test.cpp"
insert_into_file "src/#{dir}/test/CMakeLists.txt",
"#{dir}/test/#{name}Test ", :after => "set(Local "
end
Here's the template:
<% test_name = name + "Test" %>
#include <gtest/gtest.h>
#include "<%= dir %>/<%= name %>.h"
class <%= test_name %> : public testing::Test {
protected:
<%= test_name %> () {}
~<%= test_name %> () {}
virtual void SetUp () {}
virtual void TearDown () {}
};
// Don't forget to write your tests before you write your implementation!
TEST_F (<%= test_name %>, Sample) {
ASSERT_EQ(1 + 1, 3);
}
What do I have to do to get name and dir into scope here? I have more complex templates that I need this functionality for too.
I realize you already solved this, but I'm posting this answer in case someone else turns up looking for the solution to the question you asked (as I was).
Inside the class that #test belongs to, make an attr_accessor, then set its value in the same method that calls the template.
class MyGenerator < Thor
attr_accessor :name, :dir
def test (name, dir)
self.name = name
self.dir = dir
template "tasks/templates/new_test_file", "src/#{dir}/test/#{name}Test.cpp"
end
end
Note: that if you chain methods using #invoke, then a new instance of the class will be used for each invocation. Therefore you have to set the instance variable in the method with the template call. For example, the following wont work.
class MyGenerator < Thor
attr_accessor :name
def one (name)
self.name = name
invoke :two
end
def two (name)
# by the time we get here, this is another instance of MyGenerator, so #name is empty
template "tasks/templates/new_test_file", "src/#{name}Test.cpp"
end
end
You should put self.name = name inside #two instead
For making generators, if you inherit from Thor::Group instead, all the methods are called in order, and the attr_accessor will be set up for you with the instance variables set for each method. In my case, I had to use Invocations instead of Thor::Group because I couldn't get Thor::Group classes to be recognized as subcommands of an executable.
ERB uses ruby's binding object to retrieve the variables that you want. Every object in ruby has a binding, but access to the binding is limited to the object itself, by default. you can work around this, and pass the binding that you wish into your ERB template, by creating a module that exposes an object's binding, like this:
module GetBinding
def get_binding
binding
end
end
Then you need to extend any object that has the vars you want with this module.
something.extend GetBinding
and pass the binding of that object into erb
something.extend GetBinding
some_binding = something.get_binding
erb = ERB.new template
output = erb.result(some_binding)
for a complete example of working with ERB, see this wiki page for one of my projects: https://github.com/derickbailey/Albacore/wiki/Custom-Tasks

How to run arbitrary object method from string in ruby?

So I'm fairly new to ruby in general, and I'm writing some rspec test cases for an object I am creating. Lots of the test cases are fairly basic and I just want to ensure that values are being populated and returned properly. I'm wondering if there is a way for me to do this with a looping construct. Instead of having to have an assertEquals for each of the methods I want to test.
For instace:
describe item, "Testing the Item" do
it "will have a null value to start" do
item = Item.new
# Here I could do the item.name.should be_nil
# then I could do item.category.should be_nil
end
end
But I want some way to use an array to determine all of the properties to check. So I could do something like
propertyArray.each do |property|
item.#{property}.should be_nil
end
Will this or something like it work? Thanks for any help / suggestions.
object.send(:method_name) or object.send("method_name") will work.
So in your case
propertyArray.each do |property|
item.send(property).should be_nil
end
should do what you want.
If you do
propertyArray.each do |property|
item.send(property).should be_nil
end
within a single spec example and if your spec fails then it will be hard to debug which attribute is not nil or what has failed. A better way to do this is to create a separate spec example for each attribute like
describe item, "Testing the Item" do
before(:each) do
#item = Item.new
end
propertyArray.each do |property|
it "should have a null value for #{property} to start" do
#item.send(property).should be_nil
end
end
end
This will run your spec as a different spec example for each property and if it fails then you will know what has failed. This also follows the rule of one assertion per test/spec example.
A couple points about Object#send()...
You can specify parameters for the method call too...
an_object.send(:a_method, 'A param', 'Another param')
I like to use this other form __send__ because "send" is so common...
an_object.__send__(:a_method)

Resources