Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to structure my RSpec test folders, files and database?

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.

like image 540
Vincent Algayres Avatar asked Aug 10 '15 13:08

Vincent Algayres


People also ask

How do I run an RSpec on a specific file?

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.

Does RSpec clean database?

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.

What RSpec method is used to create an example?

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.


1 Answers

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.

    • Fixtures are the out-of-the-box Rails method. They are created once when the test database is created. They're fast when you have many tests, because they're only created once at the beginning of the test suite. However, they warp tests because they encourage writing tests around existing fixtures, so I strongly recommend against them.
    • Factories are object creation utilities. You create the objects you need in each test and delete or roll them back at the end. Most Rails projects that use factories use FactoryGirl. That's what I recommend.

    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?.

like image 99
Dave Schweisguth Avatar answered Sep 27 '22 16:09

Dave Schweisguth