-
Notifications
You must be signed in to change notification settings - Fork 0
Test Architecture
This document is currently in draft, and will be expanded further.
This application has a large number of asynchronous jobs which require a complex setup and add a large overhead to each test. When running feature or integration tests we should prefer expectations around tests being enqueued with the correct values rather than running the tests themselves. This makes the tests less fragile and more independent. Given that jobs also run asynchronously we should write our expectations to test that the application is in the correct state with the assumption that the job is still to run.
When testing a user sign-up feature, when the sign up form has been filled in correctly, the expectations should be that the user is redirected to the correct page, the database is updated and an email job is enqueued. The next test should not invoke the email job, but instead create a signed_up user and then access their confirmation page. A third test should create a new confirmed user and then sign in. No jobs need to be run. The job class itself should be tested separately.
To test a job has been enqueued wrap the actions you expect to enqueue it in a block:
expect do
# Some actions
end.to have_enqueued_job(JobClass).with(some_model, attribute: "some value").exactly(:once)
# Or...
expect { }.to have_enqueued_job(JobClass)
If this is not possible and some jobs will have to run, we should ensure that only jobs specifically relevant to that test are run. This can be achieved by naming the tests in a perform_enqueued_jobs
block:
perform_enqueued_jobs(only: [JobClass, AnotherJob])
# Call jobs here
end
If you are not sure precisely which job classes will need to be called, restrict this to the jobs you know will be called. If the test fails, check your test.log for enqueued jobs, and add each required one to the list.
The rails_helper.rb
makes sure that the :test
queue adaptor is always used and the queue is cleared after each test.
config.before do
ActiveJob::Base.queue_adapter = :test
end
config.after do
clear_enqueued_jobs
end
If you want a test performed immediately please call ActiveJob::Base.queue_adapter = :inline
in your test (or a before block).
In order to keep the test suite running efficiently we need to ensure that each test commits as little as possible to the database. In general therefore you should use FactoryBot's build
or build_stubbed
method instead of create
whenever possible. This way the objects are loaded only into memory and not into the database. When you want the object to be as close to a created object as possible, use build_stubbed
. This will assign an id
etc. When you are testing model validation, triggering of jobs etc, you may need to call build
instead. When you are not using factories, please use new
without save
when ever possible.
To confirm that your tests do not use the database unnecessarily run the following command:
docker-compose exec FDOC=1 bin/rspec spec/your_spec.rb
This will output an analysis, with details to help you refactor your test:
[TEST PROF INFO] FactoryDoctor report
Total (potentially) bad examples: 1
Total wasted time: 00:00.046