-
Notifications
You must be signed in to change notification settings - Fork 66
Avoid testing (and production) problems with Postqresql
This is an overview of "lessons learned" when we converted our dev and test DB from sqlite3 to postgresql (April, 2017). These provide lessons in how to avoid certain problems when testing against postgresql. These problems arose, generally, from the fact that sqlite3 - not being a robust DB - lets us get away with sloppy testing practices that postgresql does not.
For these types of associations, make sure the dependent: :destroy
option is specified on the through:
association, not the association between the owner model and the join table.
Example: This is from the Job
model. It has many job-related questions, and these are accessed via a join table representing the JobQuestions
model. Here, we want to delete associated records from the join table if the the owning job is destroyed.
Wrong:
has_many :job_questions, inverse_of: :job, dependent: :destroy
has_many :questions, through: :job_questions
Although this might appear logical, since we want to delete a join table record and thus specifying the dependent: :destroy
option on that association would seem to make sense, it is not correct.
Correct:
has_many :job_questions, inverse_of: :job
has_many :questions, through: :job_questions, dependent: :destroy
In effect, we're telling Rails to delete the through
record when the job is destroyed.
NOTE: When destroying the owning object, be sure to use the destroy
method rather than the delete
method. The former allows callbacks to execute, and the dependent: :destroy
action uses a callback to destroy the associated join table record.
sqlite3 seems to provide unordered records in a specific order (ascending ID) every time, whereas postgresql does not (Rails does not guarantee any specific order when fetching unordered collections of records).
For instance, this code tests that job seekers assigned to a job developer are assigned to an instance variable in the controller action:
it 'assigns job seekers to the job developer' do
patch :update, id: jd_person, agency_person: person_hash
expect(assigns(:agency_person).as_jd_job_seeker_ids)
.to eq [job_seeker.id, adam.id]
end
The action is not fetching the records in any order, yet the test assumes a fixed ordering every time.
Instead, this test for the same assignment but does not assume any ordering in the fetched records:
it 'assigns job seekers to the job developer' do
patch :update, id: jd_person, agency_person: person_hash
expect(assigns(:agency_person).as_jd_job_seeker_ids)
.to contain_exactly(job_seeker.id, adam.id)
end
Here is another example using a JSON structure:
expect(JSON.parse(subject.body))
.to eq('results' =>
[
{ 'id' => @jd1.id,
'text' => @jd1.full_name },
{ 'id' => @jd2.id,
'text' => @jd2.full_name },
{ 'id' => @jd3.id,
'text' => @jd3.full_name },
{ 'id' => @jd4.id,
'text' => @jd4.full_name }
])
results = JSON.parse(subject.body)
expect(results).to include('results')
expect(results['results'])
.to include({ 'id' => @jd1.id,
'text' => @jd1.full_name },
{ 'id' => @jd2.id,
'text' => @jd2.full_name },
{ 'id' => @jd3.id,
'text' => @jd3.full_name },
'id' => @jd4.id,
'text' => @jd4.full_name)
Generally we don't want to write code that is tailored to ease of testing. However, in certain circumstances this might be viable if the cost is not great and it makes for cleaner and straight-forward test statements.
For instance, this test code assumes a certain ordering for the method parameter notify_list
:
expect(Pusher).to have_received(:trigger)
.with('pusher_control',
'job_posted',
job_id: job.id,
job_title: job.title,
company_name: company.name,
notify_list: [job_developer.user.id, job_developer1.user.id])
In the code that actually calls the tested method, the collection is sorted:
jd_ids = job_developers.map { |jd| jd.user.id }.sort # sort for test purposes
Note the comment in the code - this is important because a reader may not understand why the collection is being sorted.