I have been developing in RoR for more than a year now, but I'm just starting to use tests, using RSpec.
For standard model/controller tests, I usually don't have any problem, but the issue is that I want to test some complicated functional processes, and don’t really know how to structure my testing folders/files/database.
Here is a basic structure for my application:
class Customer
has_one :wallet
has_many :orders
has_many :invoices, through: :orders
has_many :invoice_summaries
end
class Wallet
belongs_to :customer
end
class Order
has_one :invoice
belongs_to :customer
end
class Invoice
belongs_to :order
belongs_to :invoice_summary
end
class InvoiceSummary
belongs_to :customer
has_many :invoices
end
The main issue is that I want to simulate the lifecycle of my object, meaning:
Instantiating customers and wallets which will be used for all the tests (without reinitializing)
Simulating the time flow, creating and updating multiple orders/invoice objects and some invoice_summaries.
For the creation and update of the orders/invoices/invoice_summaries, I would like to have methods like
def create_order_1
# code specific to create my first order, return the created order
end
def create_order_2
# code specific to create my second order, return the created order
end
.
.
.
def create_order_n
# code specific to create my n-th order, return the created order
end
def bill_order(order_to_bill)
# generic code to do the billing of the order passed as parameter
end
def cancel_order(order_to_cancel)
# generic code to cancel the order passed as parameter
end
I’ve already found the gem Timecop for simulating the time flow. Hence, I would like to have an easy to understand final test that looks like
# Code for the initialization of customers and wallets object
describe "Wallet should be equal to 0 after first day" do
Timecop.freeze(Time.new(2015,7,1))
first_request = create_request_1
first_request.customer.wallet.value.should? == 0
end
describe "Wallet should be equal to -30 after second day" do
Timecop.freeze(Time.new(2015,7,2))
bill_order(first_request)
second_order = create_order_2
first_request.customer.wallet.value.should? == -30
end
describe "Wallet should be equal to -20 after third day" do
Timecop.freeze(Time.new(2015,7,3))
bill_order(second_request)
cancel_order(first_request)
first_request.customer.wallet.value.should? == -20
end
describe "Three first day invoice_summary should have 3 invoices" do
Timecop.freeze(Time.new(2015,7,4))
invoice_summary = InvoiceSummary.create(
begin_date: Date.new(2015,7,1),
end_date: Date.new(2015, 7,3)
) # real InvoiceSummary method
invoice_summary.invoices.count.should? == 3
end
Does anyone already have such tests? Are there good practices for the structuring of object factories, writing tests and so on?
For example, I've been told that a good idea would be to put the Customer/Wallet creation in a db/seed.rb file, but I don't really know what to do with it afterwards.
To run a single Rspec test file, you can do: rspec spec/models/your_spec. rb to run the tests in the your_spec. rb file.
I use the database_cleaner gem to scrub my test database before each test runs, ensuring a clean slate and stable baseline every time. By default, RSpec will actually do this for you, running every test with a database transaction and then rolling back that transaction after it finishes.
The describe Keyword The word describe is an RSpec keyword. It is used to define an “Example Group”. You can think of an “Example Group” as a collection of tests. The describe keyword can take a class name and/or string argument.
Fully answering your question could fill and has filled books, so I can only outline an answer here.
Regarding creating objects to test,
db/seeds.rb isn't for test data, but for static data, not changed by users, that's required for the application to run, whether in development or testing or production. Common examples of this kind of data include country codes and user roles.
There are two general approaches to creating test data, fixtures and factories.
Both approaches put object creation code in its own files in a different directory than your specs, which keeps it from junking up your spec files. If you search SO for "fixtures or factories" you'll find lots more discussion of both.
Your specs will be easier to understand if you put all of the important values, which in this case include dates and amounts, in the specs where they can be seen and compared to the results that you're asserting. (Otherwise you need to memorize the dates and the amounts in your test objects to understand the specs.) You could give the object creation methods in your test date
and amount
parameters. You might need less methods then. If you used FactoryGirl it might just be a question of specifying each object's created_at
and amount
attributes. Note also that Rails has methods like 1.day.from_now
; if you create objects with dates specified that way you might not need timecop.
Regarding how to lay out RSpec specs in the filesystem, at the top level, just make the layout identical to that of your Rails app:
app/
controllers/
bars_controller.rb
foos_controller.rb
models/
bar.rb
foo.rb
...
...
spec/
controllers/
bars_controller_spec.rb
foos_controller_spec.rb
...
models/
bar_spec.rb
foo_spec.rb
...
...
If your specs for a single class get too big, it's a sign that the class is too big. Find some pattern to break it up and test the pieces individually. If you really can't break up the class (a rare situation), turn the class's spec file into a directory of spec files, as I described in How to break down super long specs in RSpec?.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With