I have a Sale and dependent SaleItem classes
class Sale
has_many :sale_items
end
class SaleItem
belongs_to :sale
end
I have a Factories for both of these...
FactoryGirl.define do
factory :sale_item do
sale
end
end
FactoryGirl.define do
factory :sale do
sequence(:name) {|n| "Name #{n}" }
factory :sale_with_sale_items do
after :build do |sale|
build_list :sale_item, 5, :sale => sale
end
end
end
end
I then create the object from a test function as...
object = create :sale_with_sale_items
assert object.save, 'Error saving sale'
Despite following the original document closely I still get...
Minitest::UnexpectedError: ActiveRecord::RecordInvalid: Validation failed: Sale must have at least one sale item.
test/models/sale_test.rb:14:in `block in <class:SaleTest>'
test/models/sale_test.rb:14:in `block in <class:SaleTest>'
Any ideas on where this could be going wrong?
Related
Suppose that we have an object that has a vertical dependency from another
class Ship
def initialize
...
end
def launch
ShipLauncher.new(self, platform: platform)
end
end
class ShipLauncher
def initialize(ship, platform:)
...
end
end
And we want to test it:
it do
allow(ShipLauncher).to receive(:new)
ship = Ship.new
ship.launch
expect(ShipLauncher).to have_received(:new).with(ship, platform: 'x')
end
Until now all seems good, but if we change the ShipLauncher class like this
class ShipLauncher
def initialize(ship, platform_number:)
...
end
end
The test will pass when it shouldn't because the ShipLauncher class expects another parameter. What I'm doing wrong? I must test it with an integration test? What happen if the ShipLauncher class hides a big complexity? I have to stub all its dependencies?
There's a feature called "partial doubles" that can be used to check for this.
First, you need to enable it:
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
Then write your spec like this:
describe Ship do
it "launches a ship" do
ship = Ship.new
expect(ShipLauncher).to receive(:new).with(ship, platform: 'x')
ship.launch
end
end
This will pass with the original code:
> rspec ship_spec.rb
.
Finished in 0.00429 seconds (files took 0.19664 seconds to load)
1 example, 0 failures
Now, make the change:
class ShipLauncher
def initialize(ship, platform_number:)
...
end
end
And you will get this error:
rspec ship_spec.rb
F
Failures:
1) Ship should receive new
Failure/Error: expect(ShipLauncher).to receive(:new).with(ship, platform: 'x')
Missing required keyword arguments: platform_number
# ./ship_spec.rb:30:in `block (2 levels) in <top (required)>'
Finished in 0.00437 seconds (files took 0.2022 seconds to load)
1 example, 1 failure
Note that the spec is not actually calling the initialize method on ShipLauncher, which you can verify:
class ShipLauncher
def initialize(ship, platform_number:)
raise Exception, "This code should not be run!"
end
end
Run the spec, and you'll get the same results in both cases. The partial double is simply checking that the arguments and method names match the object being mocked out.
A hypothetical scenario to ask my question:
Let's say we have 2 entities: a boss and an employee. When an employee completes a job, he/she wants to let people know. In this case, the boss 'kind of' subscribes to the message and decides what to do
I implemented this interface which has an abstract method that any other class can implemented. In this (cause he/she will decide what he/she wants to do):
classdef (Abstract) Interface < handle
methods
getJobsCompleted(n)
end
end
The boss class inherits Interface and implements the method getJobsCompleted()
classdef Boss < Interface & handle
properties
myEmployee
end
methods
function this = Boss()
this.myEmployee = Employee(this)
this.myEmployee.doJobs();
end
%My boss implements (i.e. decides what to do) the abstract method
function getJobsCompleted(n)
%DO SOMETHING with n
end
end
end
And, finally, the employee performs the jobs and notifies the boss.
classdef Employee < handle
properties
numJobsCompleted;
boss = [];%pointer or reference to Boss instance
end
methods
function this = Employee(myBoss)
this.boss = myBoss; %reference/pointer to my boss so I know who to notify
end
function doJobs()
%% do something then let boss know
this.numJobsCompleted = 40;
this.boss.getJobsCompleted(this.numJobsCompleted);
end
end
end
What I have been attempting to unsuccessfully do is to pass a reference to the Employee class so that he/she knows which boss to notify.
i.e.
in Boss
this.myEmployee = Employee(this)
This will work, you just need to explicitly accept the object instance as an input argument to all methods. You'll need to update the following two function definitions:
function doJobs(this)
function getJobsCompleted(this, n)
That being said, a better way of doing this may be to use events and listeners. You would then have the employee emit a "JobCompleted" event and have the boss listen to those events for all of their employees. This prevents the employee from needing to keep track of their boss.
classdef Boss < handle
properties
Employees
Listeners
end
methods
function this = Boss(employees)
this.Employees = employees;
this.Listeners = addlistener(employees, 'JobCompleted', #this.onJobCompleted);
end
function onJobCompleted(this, employee, evnt)
fprintf('%s completed a job!\n', employee.Name);
end
end
end
Employee.m
classdef Employee < handle
properties
Name
CompletedJobs = 0
end
events
JobCompleted
end
methods
function this = Employee(name)
this.Name = name;
end
function doJob(this)
this.CompletedJobs = this.CompletedJobs + 1;
notify(this, 'JobCompleted')
end
end
end
And use it like:
employees(1) = Employee('Fred');
employees(2) = Employee('Bill');
boss = Boss(employees);
doJob(employees);
doJob(employees(1));
I have a form object that handles 2 associated objects. I'm trying to get some validation working on the form object, but am not having much success.
I think the issue is that the attributes I'm validating are delegated to the model objects, but I'm not sure how else to handle this situation: when the form is displayed, these attributes need to pull from the actual models to show the values on the form in edit mode, but when I update the form, I need to validate the submitted parameters, not the values being pulled from the models themselves.
One thought I have is using a EditBorrowerForm object that pulls attributes from associated models and a UpdateBorrowerForm that performs all the validations and updates the models. But that seems like a lot of extra work for something trivial.
I've seen a lot of form objects used for CREATE actions (where you only validate fields before passing them to the models), but haven't seen any good examples of UPDATE actions (where you need to both pull fields from models as well as update them).
Here are the models I'm working with:
class Client < ActiveRecord::Base
has_many :borrowers
end
class Borrower < ActiveRecord::Base
belongs_to :client
end
And my form object:
class BorrowerForm
include ActiveModel::Model
attr_accessor :client, :borrower
delegate :first_name, :last_name, :date_of_birth, to: :client
delegate :citizenship, :marital_status, to: :borrower
validates :first_name, :last_name, :citizenship, presence: true
def initialize(borrower)
#borrower = borrower
#client = #borrower.client
end
def update_attributes(params)
# Never validates properly...
if valid?
ActiveRecord::Base.transaction do
borrower.update_attributes(params.slice(:citizenship, :marital_status)
client.update_attributes(params.slice(:first_name, :last_name, :date_of_birth)
end
true
else
false
end
end
end
And the controller:
class BorrowersController < ApplicationController
def edit
#borrower = BorrowerForm.new(Borrower.find(params[:id])
end
def update
#borrower = BorrowerForm.new(Borrower.find(params[:id])
if #borrower.update_attributes(params)
redirect_to :index
else
render :edit
end
end
end
From my understanding, the return value from a factory's 'to_create' method is ignored. This means that the object returned from the 'build' or 'initialize_with' portion of the factory is the object ultimately returned when calling 'create' within a test.
In my case, I am using a variant of the Repository Pattern. I am overriding the 'to_create' portion of the factory to include a call to a repository 'save' method. This method does not modify the given object, but returns an object representing the persisted form of the original.
However, the instance returned from the 'build' block is returned from the factory, and not the instance created in the 'to_create' block. In my code, this means the "unpersisted" form of the object is returned, not the object with updated attributes (e.g. 'id') from the saving action.
Is there a way of forcing the return value of 'create' to be either the result of the 'to_create' block or some value generated within that block?
class Foo
attr_accessor :id, :name
...
end
class FooRepository
def self.create(name)
Foo.new(name) # this object is not yet persisted and has no .id
end
def self.save(foo)
# this method must not guarantee that the original Foo instance
# will always be returned
...
updated_foo # this is a duplicate of the original object
end
...
end
FactoryGirl.define do
factory :foo, class: FooRepository do
# create an example Foo
initialize_with { FooRepository.create(name: "Example") }
# save the Foo to the datastore, returning what may be a duplicate
to_create {|instance| FooRepository.save(instance)}
end
end
describe FooRepository do
it "saves the given Foo to the datastore" do
foo = create(:foo)
foo.id #=> nil
...
end
end
I don't have an answer for you beyond "raise an issue", sorry.
The default to_create callback looks like this:
$ grep to_create lib/factory_girl/configuration.rb
to_create {|instance| instance.save! }
The main problem is that ActiveRecord modifies itself in place when you call save! on it. FactoryGirl will ignore any new objects that are returned from to_create.
A quick hack if you want to override the default create strategy:
module FactoryGirl
module Strategy
class Create
def association(runner)
runner.run
end
def result(evaluation)
evaluation.object.tap do |instance|
evaluation.notify(:after_build, instance)
evaluation.notify(:before_create, instance)
instance = evaluation.create(instance) # <-- HACK
evaluation.notify(:after_create, instance)
end
end
end
end
end
... Or do this to your to_create hook to mimic Rails' in-place modification:
to_create do |record|
new_record = YourRepositoryHere.new.create(record)
record.attributes = new_record.attributes # For example
new_record # Return the record just in case the bug is fixed
end
Best of luck. :(
I keep running into the same issue, and I would be surprised if I am the only person experiencing this and expect someone has a better way of doing this. When I create a Factory which has a dependent Factory (association), then the parent model is not updated with the model that has been added. Probably easier to explain in code.
Say I have:
Factory.define :company do |a|
a.name 'Acme'
end
Factory.define :office do |a|
a.name 'London'
a.association :company, :factory => :company
end
and I execute this code:
london = Factory.create(:office)
sanfran = Factory.create(:office, :name => 'San Fran' , :company = london.company)
then if I run this test
london.company.offices.count.should eql(2)
it fails, because company Acme was instantiated before London or even San Fran were created, and because company.offices.new was not used to create the new models, the company model was never updated.
The only way I have been able to work around this issue is to write my tests as follows:
london.company(true).offices.count.should eql(2)
which forces a refresh.
However, this is really not ideal to do this every time in my tests, especially when the code it is testing should not have to rely on that.
Is there a reason you can't create the parent company first? I don't seem to have a problem getting a count from a pre-instantiated parent after creating child objects.
describe Company do
describe "office associations" do
before(:each) do
#company = Factory(:company)
end
it "should have the correct number of offices" do
o1 = Factory(:office, :company => #company)
o2 = Factory(:office, :company => #company)
#company.offices.should =~ [o1, o2].flatten # <= Not sure why, but each call to Factory appears to return an array
end
end