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
Related
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
I was wondering if you could yield a method call in Ruby. What I would like to do is the following:
do_in_thread { send_http_request(result) }
def do_in_thread
Thread.new { yield }
end
I basically want that method to be called in a Thread if the method is inside the block of the method do_in_thread().
Isn't it already doing so?
def do_in_thread
Thread.new { yield }
end
puts Thread.current.id
# 70059539355960
do_in_thread { puts Thread.current.id }
# 70059432631980
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.
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.
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