Factory Doctor
One common bad pattern that slows our tests down is unnecessary database manipulation. Consider a bad example:
# with FactoryBot
it "validates name presence" do
user = create(:user)
user.name = ""
expect(user).not_to be_valid
end
# with Fabrication
it "validates name presence" do
user = Fabricate(:user)
user.name = ""
expect(user).not_to be_valid
endHere we create a new user record, run all callbacks and validations and save it to the database. We don't need all these! Here is a good example:
# with FactoryBot
it "validates name presence" do
user = build_stubbed(:user)
user.name = ""
expect(user).not_to be_valid
end
# with Fabrication
it "validates name presence" do
user = Fabricate.build(:user)
user.name = ""
expect(user).not_to be_valid
endRead more about build_stubbed.
FactoryDoctor is a tool that helps you identify such bad tests, i.e. tests that perform unnecessary database queries.
Example output:
[TEST PROF INFO] FactoryDoctor report
Total (potentially) bad examples: 2
Total wasted time: 00:13.165
User (./spec/models/user_spec.rb:3) (3 records created, 00:00.628)
validates name (./spec/user_spec.rb:8) – 1 record created, 00:00.114
validates email (./spec/user_spec.rb:8) – 2 records created, 00:00.514NOTE: have you noticed the "potentially" word? Unfortunately, FactoryDoctor is not a magician (it's still learning) and sometimes it produces false negatives and false positives too.
Please, submit an issue if you found a case which makes FactoryDoctor fail.
You can also tell FactoryDoctor to ignore specific examples/groups. Just add the :fd_ignore tag to it:
# won't be reported as offense
it "is ignored", :fd_ignore do
user = create(:user)
user.name = ""
expect(user).not_to be_valid
endInstructions
FactoryDoctor supports:
- FactoryBot
- Fabrication.
RSpec
To activate FactoryDoctor use FDOC environment variable:
FDOC=1 rspec ...Using with RSpecStamp
FactoryDoctor can be used with RSpec Stamp to automatically mark bad examples with custom tags. For example:
FDOC=1 FDOC_STAMP="fdoc:consider" rspec ...After running the command above all potentially bad examples would be marked with the fdoc: :consider tag.
Minitest
In Minitest 6+, you must first activate TestProf plugin by adding Minitest.load :test_prof in your test helper.
To activate FactoryDoctor use FDOC environment variable:
FDOC=1 ruby ...or use CLI option as shown below:
ruby ... --factory-doctorThe same option to force Factory Doctor to ignore specific examples is also available for Minitest. Just use fd_ignore inside your example:
# won't be reported as offense
it "is ignored" do
fd_ignore
@user.name = ""
refute @user.valid?
endUsing with Minitest::Reporters
If you're using Minitest::Reporters in your project you have to explicitly declare it in your test helper file:
require 'minitest/reporters'
Minitest::Reporters.use! [YOUR_FAVORITE_REPORTERS]NOTE: When you have minitest-reporters installed as a gem but not declared in your Gemfile make sure to always prepend your test run command with bundle exec (but we sure that you always do it). Otherwise, you'll get an error caused by Minitest plugin system, which scans all the entries in the $LOAD_PATH for any minitest/*_plugin.rb, thus initialization of minitest-reporters plugin which is available in that case doesn't happens correctly.
Configuration
The following configuration parameters are available (showing defaults):
TestProf::FactoryDoctor.configure do |config|
# Which event to track within test example to consider them "DB-dirty"
config.event = "sql.active_record"
# Consider result "good" if the time in DB is less then the threshold
config.threshold = 0.01
endYou can use the corresponding env variables as well: FDOC_EVENT and FDOC_THRESHOLD.