Rspec, scope di un metodo before

Prima l’esempio dummy:

class Product < ActiveRecord::Base
validates :code, uniqueness: true

def clone
Product.create(code: “New #{code}”, description: description)
end
end

describe Product, type: :model do
it “is valid” do
expect(build(:product)).to be_valid
end

describe “cloned object” do
before :context do
@pr = create(:product)
@cl = @pr.clone
end

it "has different code" do
  expect(@cl.code).not_to eq(@pr.code)
end

it "has same description" do
  expect(@cl.description).to eq(@pr.description)
end

it ...

end

end

La descrizione del mio problema:

Vorrei testare il corretto comportamento del metodo ‘clone’ di Product
verificando che la copia abbia tutti gli attributi uguali (tranne
l’attributo code).
Per fare ciò ho inserito un blocco ‘before :context’ che mi crea
l’originale e il suo clone e poi vado a testare ogni attributo.
Inserisco il tutto in un blocco ‘describe’ (o ‘context’) con la
convinzione che il before venga eseguito solo per gli esempi all’interno
di esso.
E invece no: il blocco ‘before :context’ viene eseguito anche fuori dal
blocco, nell’esempio mostrato viene eseguito anche prima dell’esempio
‘is valid’, e la validazione non passa in quanto il build prepara un
record con un code già esistente.

Infine le domande:

  • è possibile separare il metodo ‘before’ per applicarlo solo ad un
    sottoinsieme di esempi?
  • quale è la best practice in una situazione del genere?

Grazie mille per tutti i contributi e le risposte.

Ciao

i.

usa before(:each) do

Alessandro R.

Ciao Iwan,
ho provato a replicare il tuo esempio in maniera simile e nel mio caso
il blocco “before(:context)” viene eseguito solo una volta: prima dei
due blocchi “it” come da manuale.
Ti riporto qui sotto il codice (classe+spec), puoi benissimo copiarlo e
incollarlo in un file.rb per testarlo a tua volta.
(per praticità ho sostituito la validazione dell’unicità del record con
la validazione della presenza).

---- product_spec.rb

require “rspec/expectations”
require “active_model”

class Product
include ActiveModel::Validations

attr_accessor :code, :description
validates :code, :description, presence: true

def initialize params = {}
params.each { |key, value| send “#{key}=”, value }
end

def clone
product = Product.new code: “New #{self.code}”, description:
self.description
end
end

SPEC

RSpec.describe Product do

it “is valid” do
expect(Product.new code: “1234”, description: “description”).to
be_valid
end

it “isn’t valid without code” do
expect(Product.new code: nil, description: “description”).to
be_valid
end

describe “cloned object” do
before(:context) do
puts “before context → creo product e lo clono”
@pr = Product.new code: “1234”, description: “description”
@cl = @pr.clone
end

it "has different code" do
  expect(@cl.code).not_to eq(@pr.code)
end

it "has same description" do
  expect(@cl.description).to eq(@pr.description)
end

end

end

----------------------------

se lancio il test da terminale l’output è il seguente:

Nico$ rspec test2.rb --format documentation

Product
is valid
isn’t valid without code
cloned object
before context → creo product e lo clono
has different code
has same description

Finished in 0.20649 seconds (files took 0.45466 seconds to load)
4 examples, 0 failures

Come vedi il “before” block viene chiamato una volta sola all’interno
della describe “cloned object” e prima dei 2 blocchi “it” al suo
interno.

Personalmente preferisco evitare l’uso di variabili di istanza se non è
strettamente necessario e uso let al suo posto.
Dai un occhio qui

Ciao :slight_smile:

Nicolò