require 'spec_helper' TRANSFORMS_SUPPORTED = Cucumber::VERSION < '3' describe 'steps provided by cucumber_factory' do before(:each) do prepare_cucumber_example end context 'FactoryBot' do it "should create ActiveRecord models by calling #new and #save!" do movie = Movie.new Movie.should_receive(:new).with(no_args).and_return(movie) movie.should_receive(:save!) invoke_cucumber_step("there is a movie") end it "should create models that have a factory_bot factory by calling #FactoryBot.create(:model_name)" do FactoryBot.should_receive(:create).with(:job_offer, { :title => "Awesome job" }) invoke_cucumber_step('there is a job offer with the title "Awesome job"') end it "should create model variants that have a factory_bot factory by calling #FactoryBot.create(:variant)" do FactoryBot.should_receive(:create).with(:job_offer, :tempting_job_offer, { :title => "Awesomafiablyfantasmic job" }) invoke_cucumber_step('there is a job offer (tempting job offer) with the title "Awesomafiablyfantasmic job"') end it "should create model variants that have a factory_bot trait by calling #FactoryBot.create(:factory, :trait1, :trait2)" do FactoryBot.should_receive(:create).with(:job_offer, :risky, :lucrative, { :title => "Awesomafiablyfantasmic job" }) invoke_cucumber_step('there is a job offer (risky, lucrative) with the title "Awesomafiablyfantasmic job"') end it "should create model variants that have a factory_bot factory by using the model name as a factory name" do FactoryBot.should_receive(:create).with(:job_offer, { :title => "Awesomafiablyfantasmic job" }) invoke_cucumber_step('there is a job offer with the title "Awesomafiablyfantasmic job"') end it "should instantiate classes with multiple words in their name" do job_offer = JobOffer.new JobOffer.should_receive(:new).with(no_args).and_return(job_offer) invoke_cucumber_step("there is a job offer") end it "should instantiate classes with uppercase characters in their name" do user = User.new User.should_receive(:new).and_return(user) invoke_cucumber_step("there is a User") end it "should allow either 'a' or 'an' for the article" do opera = Opera.new Opera.should_receive(:new).with(no_args).and_return(opera) invoke_cucumber_step("there is an opera") end it "should create records with attributes" do movie = Movie.new Movie.stub(:new => movie) invoke_cucumber_step('there is a movie with the title "Sunshine" and the year "2007"') movie.title.should == "Sunshine" movie.year.should == 2007 end it "should allow to join attribute lists with 'and's, commas and 'but's" do movie = Movie.new Movie.stub(:new => movie) invoke_cucumber_step('there is a movie with the title "Sunshine", the year "2007" but with the box office result "32000000"') movie.title.should == "Sunshine" movie.year.should == 2007 movie.box_office_result.should == 32000000 end if TRANSFORMS_SUPPORTED it "should apply Cucumber transforms to attribute values" do movie = Movie.new Movie.stub(:new => movie) @main.instance_eval do Transform /^(value)$/ do |value| 'transformed value' end end invoke_cucumber_step('there is a movie with the title "value"') movie.title.should == "transformed value" end end it "should create records with attributes containing spaces" do movie = Movie.new Movie.stub(:new => movie) invoke_cucumber_step('there is a movie with the box office result "99999999"') movie.box_office_result.should == 99999999 end it "should create records with attributes containing uppercase characters" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a User with the Name "Susanne"') user.name.should == "Susanne" end it "should override attr_accessible protection" do invoke_cucumber_step('there is a payment with the amount "120" and the comment "Thanks for lending"') payment = Payment.last payment.amount.should == 120 payment.comment.should == 'Thanks for lending' end it "should allow to set an explicit primary key" do invoke_cucumber_step('there is a payment with the ID 2') payment = Payment.last payment.id.should == 2 end it "should allow to name records and set a belongs_to association to that record by referring to that name" do invoke_cucumber_step('"Some Prequel" is a movie with the title "Before Sunrise"') invoke_cucumber_step('there is a movie with the title "Limitless"') invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"') movie = Movie.find_by_title!('Before Sunset') prequel = Movie.find_by_title!('Before Sunrise') movie.prequel.should == prequel end it "should allow to set a belongs_to association to a previously created record by referring to any string attribute of that record" do invoke_cucumber_step('there is a movie with the title "Before Sunrise"') invoke_cucumber_step('there is a movie with the title "Limitless"') invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Before Sunrise"') movie = Movie.find_by_title!('Before Sunset') prequel = Movie.find_by_title!('Before Sunrise') movie.prequel.should == prequel end it "should allow to set a belongs_to association to a previously updated record by referring to any string attribute used when updating" do invoke_cucumber_step('there is a movie') invoke_cucumber_step('the movie above has the title "Before Sunrise"') invoke_cucumber_step('there is a movie with the title "Limitless"') invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Before Sunrise"') movie = Movie.find_by_title!('Before Sunset') prequel = Movie.find_by_title!('Before Sunrise') movie.prequel.should == prequel end it "should allow to set a belongs_to association to a previously created record by referring to their explicitely set primary keys" do invoke_cucumber_step('there is a movie with the ID 123') invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel 123') movie = Movie.find_by_title!('Before Sunset') prequel = Movie.find(123) movie.prequel.should == prequel end it "should allow to set a belongs_to association to a previously created record by saying 'above'" do invoke_cucumber_step('there is a user with the name "Jane"') invoke_cucumber_step('there is a user with the name "John"') invoke_cucumber_step('there is a movie with the title "Limitless"') invoke_cucumber_step('there is a movie with the title "Before Sunrise"') invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above and the prequel above') before_sunset = Movie.find_by_title!("Before Sunset") before_sunset.prequel.title.should == "Before Sunrise" before_sunset.reviewer.name.should == "John" end if TRANSFORMS_SUPPORTED it "should fallback to using transforms when no named record is found" do user = User.create!(:name => 'Me') @main.instance_eval do Transform(/^(me)$/) do |value| user end end invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer "me"') before_sunset = Movie.find_by_title!("Before Sunset") before_sunset.reviewer.should == user end end it "should give created_at precedence over id when saying 'above' if the primary key is not numeric" do invoke_cucumber_step('there is a uuid user with the name "Jane" and the id "jane"') invoke_cucumber_step('there is a uuid user with the name "John" and the id "john"') UuidUser.find_by_name("John").update_attributes!(:created_at => 1.day.ago) invoke_cucumber_step('there is a movie with the title "Before Sunset" and the uuid reviewer above') before_sunset = Movie.find_by_title!("Before Sunset") before_sunset.uuid_reviewer.name.should == "Jane" end it "should ignore created_at if the primary key is numeric" do invoke_cucumber_step('there is a user with the name "Jane"') invoke_cucumber_step('there is a user with the name "John"') User.find_by_name("John").update_attributes!(:created_at => 1.day.ago) invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above') before_sunset = Movie.find_by_title!("Before Sunset") before_sunset.reviewer.name.should == "John" end it "should raise a proper error if there is no previous record when saying 'above'" do lambda do invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above and the prequel above') end.should raise_error(/There is no last reviewer/i) end it "should reload an object assigned to a belongs_to before assigning" do invoke_cucumber_step('"Jane" is a user who is deleted') User.last.update_attributes(:deleted => false) proc { invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer "Jane"') }.should_not raise_error end it "should allow to set positive boolean attributes with 'who' after the attribute list" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user with the name "Jane" who is deleted') user.name.should == "Jane" user.deleted.should == true end it "should allow to name records and set a belongs_to association to that record by referring to that name" do invoke_cucumber_step('"Some Prequel" is a movie with the title "Before Sunrise"') invoke_cucumber_step('there is a movie with the title "Limitless"') invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"') movie = Movie.find_by_title!('Before Sunset') prequel = Movie.find_by_title!('Before Sunrise') movie.prequel.should == prequel end it "should allow to set positive boolean attributes with 'which' after the attribute list" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user with the name "Jane" which is deleted') user.name.should == "Jane" user.deleted.should == true end it "should allow to set positive boolean attributes with 'that' after the attribute list" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user with the name "Jane" that is deleted') user.name.should == "Jane" user.deleted.should == true end it "should allow to set boolean attributes without regular attributes preceding them" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user who is deleted') user.deleted.should == true end it "should allow to set negative boolean attribute" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user who is not deleted') user.deleted.should == false end it "should allow to set multiple boolean attributes" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user who is locked and not deleted and subscribed') user.locked.should == true user.deleted.should == false user.subscribed.should == true end it "should allow to set boolean attributes that are named from multiple words" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user who is locked and not scared and scared by spiders and deleted') user.locked.should == true user.scared.should == false user.scared_by_spiders.should == true user.deleted.should == true end it "should allow to join boolean attribute lists with 'and's, commas and 'but's" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user who is locked, scared, but scared by spiders and deleted') user.locked.should == true user.scared.should == true user.scared_by_spiders.should == true user.deleted.should == true end it "should allow to set a has_many association by referring to multiple named records in square brackets" do invoke_cucumber_step('there is a movie with the title "Sunshine"') invoke_cucumber_step('there is a movie with the title "Limitless"') invoke_cucumber_step('there is a user with the reviewed movies ["Sunshine" and "Limitless"]') user = User.last reviewed_movie_titles = user.reviewed_movies.map(&:title) reviewed_movie_titles.should =~ ['Sunshine', 'Limitless'] end it 'allow associations for transient attributes if they are named after the associated model' do invoke_cucumber_step('there is a movie with the title "Sunshine"') invoke_cucumber_step('there is a user with the movie "Sunshine"') user = User.last user.reviewed_movies.count.should == 1 user.reviewed_movies.first.title.should == 'Sunshine' end it "should allow to set attributes via doc string" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user with these attributes:', <<-DOC_STRING) name: Jane locked: true DOC_STRING user.name.should == "Jane" user.locked.should == true end it "should allow to set attributes via additional doc string" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', <<-DOC_STRING) name: Jane DOC_STRING user.name.should == "Jane" user.email.should == "test@invalid.com" end it 'should allow named records when setting attributes via doc string' do invoke_cucumber_step('"Some Prequel" is a movie with these attributes:', <<-DOC_STRING) title: Before Sunrise DOC_STRING invoke_cucumber_step('there is a movie with the title "Limitless"') invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"') movie = Movie.find_by_title!('Before Sunset') prequel = Movie.find_by_title!('Before Sunrise') movie.prequel.should == prequel end it "should allow to set attributes via data table" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user with these attributes:', nil, <<-DATA_TABLE) | name | Jane | | locked | true | DATA_TABLE user.name.should == "Jane" user.locked.should == true end it "should allow to set attributes via additional data table" do user = User.new User.stub(:new => user) invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', nil, <<-DATA_TABLE) | name | Jane | DATA_TABLE user.name.should == "Jane" user.email.should == "test@invalid.com" end it 'should allow named records when setting attributes via data table' do invoke_cucumber_step('"Some Prequel" is a movie with these attributes:', nil, <<-DATA_TABLE) | title | Before Sunrise | DATA_TABLE invoke_cucumber_step('there is a movie with the title "Limitless"') invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"') movie = Movie.find_by_title!('Before Sunset') prequel = Movie.find_by_title!('Before Sunrise') movie.prequel.should == prequel end it "should allow mixed single quotes for model names" do invoke_cucumber_step("'Some Prequel' is a movie with the title \"Before Sunrise\"") invoke_cucumber_step('there is a movie with the title "Limitless"') invoke_cucumber_step('there is a movie with the title \'Before Sunset\' and the prequel "Some Prequel"') movie = Movie.find_by_title!('Before Sunset') prequel = Movie.find_by_title!('Before Sunrise') movie.prequel.should == prequel end it 'supports named associations with polymorphic associations' do invoke_cucumber_step('"my opera" is an opera') invoke_cucumber_step('there is a movie with the premiere site "my opera"') end it 'does not support last record references with polymorphic associations as the target class cannot be guessed' do invoke_cucumber_step('there is an opera') expect { invoke_cucumber_step('there is a movie with the premiere site above') }.to raise_error(CucumberFactory::Factory::Error, 'Cannot set last Movie#premiere_site for polymorphic associations') end it 'supports carrierwave uploaders' do invoke_cucumber_step('there is a payment with the attachment file:"spec/assets/file.txt"') payment = Payment.last expect(payment.attachment.file.read).to eq "This is a test file.\n" end it 'supports single quote for carrierwave uploaders' do invoke_cucumber_step("there is a payment with the attachment file:'spec/assets/file.txt'") payment = Payment.last expect(payment.attachment.file.read).to eq "This is a test file.\n" end it 'is able to read symlinked files' do invoke_cucumber_step("there is a payment with the attachment file:'spec/assets/symlink.txt'") payment = Payment.last expect(payment.attachment.file.read).to eq "This is a test file.\n" end it 'supports table syntax for carrierwave uploaders' do invoke_cucumber_step('there is a payment with these attributes:', nil, <<-DATA_TABLE) | attachment | file:"spec/assets/file.txt" | DATA_TABLE payment = Payment.last expect(payment.attachment.file.read).to eq "This is a test file.\n" end it 'supports doc string syntax for carrierwave uploaders' do invoke_cucumber_step('there is a payment with these attributes:', <<-DOC_STRING) attachment: file:"spec/assets/file.txt" DOC_STRING payment = Payment.last payment.save! expect(payment.attachment.file.read).to eq "This is a test file.\n" end it 'allows updating a carrierwave attribute' do invoke_cucumber_step('there is a payment with the attachment file:"spec/assets/file.txt"') payment = Payment.last expect(payment.attachment.file.read).to eq "This is a test file.\n" invoke_cucumber_step('the payment above has the attachment file:"spec/assets/file2.txt"') payment.reload expect(payment.attachment.file.read).to eq "This is the second test file.\n" end it 'works with nested factories (BUGFIX)' do invoke_cucumber_step('there is a subgenre movie') end describe 'references to named records are tracked correctly' do # CucumberFactory keeps track of all created records by their attributes. # For example, if you create a user with the name "Peter" you can reference him later on as "Peter". it 'does not overwrite existing references when associating new records with another reference (BUGFIX)' do invoke_cucumber_step('"reviewer" is a user with the id "1"') movie1 = invoke_cucumber_step('there is a movie with the reviewer "reviewer" and the id "2"') # <- should not overwrite the 'reviewer' reference movie2 = invoke_cucumber_step('there is a movie with the reviewer "reviewer" and the id "3"') expect(movie1.reviewer_id).to eq(1) expect(movie2.reviewer_id).to eq(1) end it 'does not set new references when setting primary keys of an association (BUGFIX)' do movie1 = invoke_cucumber_step('there is a movie with the id "123" and the user id "456"') # <- should not set a '456' reference movie2 = invoke_cucumber_step('there is a movie with the user id "456"') expect(movie1.reviewer_id).to eq(456) expect(movie2.reviewer_id).to eq(456) end end end context 'without FactoryBot' do before do hide_const("FactoryBot") end it "should instantiate plain ruby classes by calling #new" do PlainRubyClass.should_receive(:new).with({}) invoke_cucumber_step("there is a plain ruby class") end it "should instantiate namespaced classes" do actor = People::Actor.new People::Actor.should_receive(:new).and_return(actor) invoke_cucumber_step("there is a people/actor") end it "should allow to set integer attributes without surrounding quotes" do invoke_cucumber_step('there is a plain Ruby class with the amount 123 and the total 456') obj = PlainRubyClass.last obj.attributes[:amount].should == 123 obj.attributes[:total].should == 456 end it "should allow to set decimal attributes without surrounding quotes" do invoke_cucumber_step('there is a plain Ruby class with the amount 1.23 and the total 45.6') obj = PlainRubyClass.last obj.attributes[:amount].should be_a(BigDecimal) obj.attributes[:amount].to_s.should == "1.23" obj.attributes[:total].should be_a(BigDecimal) obj.attributes[:total].to_s.should == "45.6" end it "should allow to set file attributes with the file:'path' syntax" do invoke_cucumber_step('there is a plain Ruby class with the file file:"spec/assets/file.txt"') obj = PlainRubyClass.last obj.attributes[:file].should be_a(File) obj.attributes[:file].read.should == "This is a test file.\n" end it "should allow set an array of strings with square brackets" do invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar"] and the list ["bam", "baz"]') obj = PlainRubyClass.last obj.attributes[:tags].should == ['foo', 'bar'] obj.attributes[:list].should == ['bam', 'baz'] end it "should allow set an array of numbers with square brackets" do invoke_cucumber_step('there is a plain Ruby class with the integers [1, 2] and the decimals [3.4, 4.5]') obj = PlainRubyClass.last obj.attributes[:integers].should == [1, 2] obj.attributes[:decimals].should == [BigDecimal('3.4'), BigDecimal('4.5')] end it 'should allow to set an empty array' do invoke_cucumber_step('there is a plain Ruby class with the tags []') obj = PlainRubyClass.last obj.attributes[:tags].should == [] end it 'should allow to separate array values with either a comma or "and"' do invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar" and "baz"] and the list ["bam", "baz" and "qux"]') obj = PlainRubyClass.last obj.attributes[:tags].should == ['foo', 'bar', 'baz'] obj.attributes[:list].should == ['bam', 'baz', 'qux'] end it 'should allow to separate array values with an Oxford comma' do invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar", and "baz"] and the list ["bam", "baz", and "qux"]') obj = PlainRubyClass.last obj.attributes[:tags].should == ['foo', 'bar', 'baz'] obj.attributes[:list].should == ['bam', 'baz', 'qux'] end it "should allow attribute names starting with 'the'" do PlainRubyClass.should_receive(:new).with({:theme => 'Sci-fi'}) invoke_cucumber_step('there is a plain ruby class with the theme "Sci-fi"') end it "should allow attribute names starting with 'and'" do PlainRubyClass.should_receive(:new).with({:android => 'Paranoid'}) invoke_cucumber_step('there is a plain ruby class with the android "Paranoid"') end it "should allow attribute names starting with 'with'" do PlainRubyClass.should_receive(:new).with({:withdrawal => 'bank_account'}) invoke_cucumber_step('there is a plain ruby class with the withdrawal "bank_account"') end it "should allow attribute names starting with 'but'" do PlainRubyClass.should_receive(:new).with({:butt => 'pear-shaped'}) invoke_cucumber_step('there is a plain ruby class with the butt "pear-shaped"') end it "should allow to set array attributes via doc string" do invoke_cucumber_step('there is a plain Ruby class with these attributes:', <<-DOC_STRING) tags: ["foo", "bar"] DOC_STRING obj = PlainRubyClass.last obj.attributes[:tags].should == ['foo', 'bar'] end it "should allow to set array attributes via data table" do invoke_cucumber_step('there is a plain Ruby class with these attributes:', nil, <<-DATA_TABLE) | tags | ["foo", "bar"] | DATA_TABLE obj = PlainRubyClass.last obj.attributes[:tags].should == ['foo', 'bar'] end it "should create models that have a machinist blueprint by calling #make" do MachinistModel.should_receive(:make).with({ :attribute => "foo"}) invoke_cucumber_step('there is a machinist model with the attribute "foo"') end it "should be able to step_match machinist blueprint variants" do MachinistModel.should_receive(:make).with(:variant, { :attribute => "foo"}) invoke_cucumber_step('there is a machinist model (variant) with the attribute "foo"') end it "should be able to step_match machinist blueprint variants containing spaces or uppercase characters in prose" do MachinistModel.should_receive(:make).with(:variant_mark_two, { :attribute => "foo"}) invoke_cucumber_step('there is a machinist model (Variant Mark Two) with the attribute "foo"') end it "should allow single quote for attribute values" do MachinistModel.should_receive(:make).with({ :attribute => "foo"}) invoke_cucumber_step("there is a machinist model with the attribute 'foo'") end it "should allow mixed single and double quotes for different attribute values" do MachinistModel.should_receive(:make).with({ :attribute => "foo", :other_attribute => "bar" }) invoke_cucumber_step("there is a machinist model with the attribute 'foo' and the other attribute \"bar\"") end it 'should not raise an error for a blank instance name' do MachinistModel.should_receive(:make).with({ :attribute => 'foo' }) invoke_cucumber_step("'' is a machinist model with the attribute 'foo'") end it 'should warn if there are unused fragments' do MachinistModel.should_not_receive(:make) lambda { invoke_cucumber_step("there is a machinist model with the attribute NOQUOTES") }.should raise_error(ArgumentError, 'Unable to parse attributes " with the attribute NOQUOTES".') lambda { invoke_cucumber_step("there is a machinist model with the attribute 'foo' and the ") }.should raise_error(ArgumentError, 'Unable to parse attributes " and the ".') lambda { invoke_cucumber_step("there is a machinist model with the attribute 'foo'. the other_attribute 'bar' and the third attribute 'baz'") }.should raise_error(ArgumentError, 'Unable to parse attributes ".".') end it "should allow to update the last record of a type" do invoke_cucumber_step('there is a user with the name "Bar" and the email "foo@example.com" who is not subscribed') invoke_cucumber_step('the user above has the name "Baz", the email "foobar@example.com", and is subscribed') user = User.last user.name.should == 'Baz' user.email.should == 'foobar@example.com' user.subscribed.should == true end it "should be able to update a record with a given name" do invoke_cucumber_step('"Foo" is a user with the name "Bar"') invoke_cucumber_step('the user "Foo" has the email "foo@example.com"') invoke_cucumber_step("the user 'Foo' is subscribed") user = User.last user.name.should == 'Bar' user.email.should == 'foo@example.com' user.subscribed.should == true end it "should be able to update a record with a Cucumber table expression" do invoke_cucumber_step('"Foo" is a user with the name "Bar"') invoke_cucumber_step('the user above has these attributes:', nil, <<-DATA_TABLE) | name | Jane | | locked | true | DATA_TABLE user = User.last user.name.should == "Jane" user.locked.should == true end it "should allow to update a record via additional data table" do invoke_cucumber_step('"Foo" is a user with the name "Bar"') invoke_cucumber_step('the user above has the email "test@invalid.com", is subscribed, and has these attributes:', nil, <<-DATA_TABLE) | name | Jane | DATA_TABLE user = User.last user.name.should == "Jane" user.email.should == "test@invalid.com" user.subscribed.should == true end it "should allow to update an association" do invoke_cucumber_step('there is a movie with the title "Before Sunrise"') invoke_cucumber_step('there is a user with the name "John"') invoke_cucumber_step('the movie above has the reviewer above') movie = Movie.last movie.reviewer.name.should == "John" end end end