Is there a better way to test this Ruby class with RSpec? - ruby

I'm extracting a subset of fields from a full JSON dataset having a JSON fixture. The better way I could think of is the following :
require "spec_helper"
# API ref.: GET /repos/:owner/:repo
# http://developer.github.com/v3/repos/
describe Elasticrepo::RepoSubset do
context "extract a subset of repository fields" do
let(:parsed) { Yajl::Parser.parse(fixture("repository.json").read) }
subject { Elasticrepo::RepoSubset.new(parsed) }
context "#id" do
its(:id) { should eq(2126244) }
end
context "#owner" do
its(:owner) { should eq("twitter") }
end
context "#name" do
its(:name) { should eq("bootstrap") }
end
context "#url" do
its(:url) { should eq("https://api.github.com/repos/twitter/bootstrap") }
end
context "#description" do
its(:description) { should eq("Sleek, intuitive, and powerful front-end framework for faster and easier web development.") }
end
context "#created_at" do
its(:created_at) { should eq("2011-07-29T21:19:00Z") }
end
context "#pushed_at" do
its(:pushed_at) { should eq("2013-04-13T03:56:36Z") }
end
context "#organization" do
its(:organization) { should eq("Organization") }
end
context "#full_name" do
its(:full_name) { should eq("twitter/bootstrap") }
end
context "#language" do
its(:language) { should eq("JavaScript") }
end
context "#updated_at" do
its(:updated_at) { should eq("2013-04-13T19:12:09Z") }
end
end
end
but I wonder if is there a better, smarter, cleaner or just more elegant way of doing that. The class I TDD out is this :
module Elasticrepo
class RepoSubset
attr_reader :id, :owner, :name, :url, :description, :created_at, :pushed_at,
:organization, :full_name, :language, :updated_at
def initialize(attributes)
#id = attributes["id"]
#owner = attributes["owner"]["login"]
#name = attributes["name"]
#url = attributes["url"]
#description = attributes["description"]
#created_at = attributes["created_at"]
#pushed_at = attributes["pushed_at"]
#organization = attributes["owner"]["type"]
#full_name = attributes["full_name"]
#language = attributes["language"]
#updated_at = attributes["updated_at"]
end
end
end

I would remove the individual context blocks. They don't add any additional information.

I'd use a map of keys/values and iterate, or create an object with the correct values and compare the entire object.

Related

How do I create multiple children for a single parent in FactoryBot?

What we want to achieve
I am using FactoryBot to create test data, and when I create a parent in create_list, I want to create 5 children for each parent, is this possible? What should I do in this case?
Code
Book is a parent.
FactoryBot.define do
factory :book, class: Post do
sequence(:id) { |n| n}
sequence(:title) { |n| "title#{n}" }
sequence(:author) { |n| "author#{n}" }
sequence(:image) { |n| "image#{n}"}
end
end
The content is the children
FactoryBot.define do
factory :content, class: PostItem do
sequence(:content) { |n| "list#{n}"}
sequence(:status) { false }
end
end
I want to make five children for each parent.
Five children can be created for each parent.
book = FactoryBot.create(:book)
content = FactoryBot.create_list(:contet, 5, post_id: book.id)
For multiple parents, I don't know how to create five children for one parent.
book = FactoryBot.create_list(:book)
content = FactoryBot.create_list(:contet, 5, post_id: ???)
A quick way to generalize this would be to use after hooks in your factory.
For instance you could do the following :
FactoryBot.define do
factory :book, class: Post do
sequence(:id) { |n| n}
sequence(:title) { |n| "title#{n}" }
sequence(:author) { |n| "author#{n}" }
sequence(:image) { |n| "image#{n}"}
# trait is optional, but nice to have. Call it with FactoryBot.create :book, :with_content
trait :with_content do
after(:create) do |object|
FactoryBot.create_list(:content, 5, post_id: object.id)
object.reload # to reload the association. You could avoid the reload by using your association (ie: object.contents = ...)
end
end
end
end
or a simplier version
FactoryBot.define do
factory :book, class: Post do
sequence(:id) { |n| n}
sequence(:title) { |n| "title#{n}" }
sequence(:author) { |n| "author#{n}" }
sequence(:image) { |n| "image#{n}"}
after(:create) do |object|
object.posts = FactoryBot.create_list(:content, 5)
end
end
end

Unit testing a method in which I am doing a dependency injection

I have a terminal app with two classes: Todo and TodoApp. The method below lives in TodoApp, I would like to unit test this method and keep it isolated in my test. Since I am doing a dependency injection within the method, how could I mock that? (#todos is an empty array in TodoApp initialize)
def add(task, day, todo = Todo)
#todos.push(todo.new(task, day))
return "#{task} was added to your todos"
end
Thanks in advance for your help.
I imagine your code looks like this.
class TodoApp
def initialize
#todos = []
end
def add(task, day, todo = Todo)
#todos.push(todo.new(task, day))
return "#{task} was added to your todos"
end
end
This is NOT injecting the empty array into the TodoApp. And, therefore you are going to have difficulty accessing it from the tests.
But, if your TodoApp object looked like this:
class TodoApp
def initialize(todos = [])
#todos = todos
end
def add(task, day, todo = Todo)
#todos.push(todo.new(task, day))
return "#{task} was added to your todos"
end
end
Now you are injecting something into TodoApp that can be mocked, or even just evaluated:
describe TodoApp do
subject(:app) { described_class.new(todos) }
let(:todos) { [] }
describe '#add' do
subject(:add) { app.add(task, day) }
let(:task) { 'task' }
let(:day) { 'day' }
it 'pushes the item on the list of todos' do
expect { add }.to change { todos.length }.by(1)
end
end
end

Ruby - calling a function for each included module

I'm working on an XML export for a Ruby project and I'm looking for an elegant way to implement it. The goal of this XML file is to export all data inside a project container and there is several models (about 20) sharing some common properties (for example a name and a description).
Currently, the XML export looks like an awful thing like that:
def export_project(p)
#xml project {
#xml.name p.name
#xml.description p.description
#xml.itemAs {
p.item_as.each {|item_a|export_itemA(item_a)
}
#xml.itemBs {
p.item_Bs.each {|item_B|export_itemB(item_b)
}
#xml.itemCs {
p.item_cs.each {|item_c|export_itemC(item_c)
}
}
end
def export_itemA(a)
#xml.itemA {
#xml.name a.name
}
end
def export_itemB(b)
#xml.itemB {
#xml.description b.description
}
end
def export_itemC(c)
#xml.itemC {
#xml.name c.name
#xml.description c.description
}
end
Which is pretty ugly (well, it's bearrable with 4 types, but the reality is 480 lines of mess ...)
What I'd like would be something like that (considered there is a magic mapping between a model and an exporter):
module Named
def export
#xml.name #context.name
end
end
module Described
def export
#xml.description #context.description
end
end
class ProjectExporter < ModelExporter
include Named
include Described
def export
#xml.project {
super
#xml.itemAs {
export_items(p.item_as)
}
#xml.itemBs {
export_items(p.item_Bs)
}
#xml.itemCs {
export_items(p.item_cs)
}
}
end
class ItemAExporter < ModelExporter
include Named
def export
#xml.itemA {
super
}
end
end
class ItemBExporter < ModelExporter
include Described
def export
#xml.itemB {
super
}
end
end
class ItemCExporter < ModelExporter
include Named
include Described
def export
#xml.itemC {
super
}
end
end
The problem with this method is that "super" will only call the export method of one of the module, not all of them.
I'm pretty suer the module and super approach is not the correct one, but I'm unable to find something more suitable. Any idea ?
Cheers and thanks,
Vincent

Having trouble with send and define_method

I'm trying to create a custom attr_accessor, but can't seem to get it to work. Instead of returning the value assigned to the writer, it returns the instance variable. Any ideas?
class Object
def custom_attr_accessor(klass, attribute)
ivar = "##{attribute}".to_sym
writer_body = lambda { |arg| instance_variable_set(ivar, arg) }
reader_body = lambda { ivar }
klass.send(:define_method, "#{attribute}=".to_sym, &writer_body)
klass.send(:define_method, "#{attribute}".to_sym, &reader_body)
end
end
class Person
end
custom_attr_accessor(Person, :age)
me = Person.new
me.age = 100
puts me.age
=> #age
Just like you did a instance_variable_set, you need instance_variable_get:
reader_body = lambda { instance_variable_get(ivar) }
BTW, extending Object and passing a class is not very pretty. Try to make it Persion. custom_attr_accessor(:age), that would be much more OOP.

How can I assert on initialize behaviour with RSpec?

I have a message class, which can be initialized by passing arguments into the constructor, or by passing no arguments and then setting the attributes later with accessors. There is some pre-processing going on in the setter methods of the attributes.
I've got tests which ensure the setter methods do what they're supposed to, but I can't seem to figure out a good way of testing that the initialize method actually calls the setters.
class Message
attr_accessor :body
attr_accessor :recipients
attr_accessor :options
def initialize(message=nil, recipients=nil, options=nil)
self.body = message if message
self.recipients = recipients if recipients
self.options = options if options
end
def body=(body)
#body = body.strip_html
end
def recipients=(recipients)
#recipients = []
[*recipients].each do |recipient|
self.add_recipient(recipient)
end
end
end
I would tend to test the behaviour of the initializer,
i.e. that its setup the variables how you would expect.
Not getting caught up in the actuality of how you do it, assume that the underlying accessors work, or alternatively you could set the instance variables if you wanted. Its almost a good old fashioned unit test.
e.g.
describe "initialize" do
let(:body) { "some text" }
let(:people) { ["Mr Bob","Mr Man"] }
let(:my_options) { { :opts => "are here" } }
subject { Message.new body, people, my_options }
its(:message) { should == body }
its(:recipients) { should == people }
its(:options) { should == my_options }
end

Resources