Referencing one trait from another trait in factory_bot

Sometimes you want to DRY up traits by referencing one trait from another trait in factory_bot. I tried searching on “inheriting traits” (that’s just for one factory inheriting traits from another and was in a factory_bot issue in GitHub). I accidentally stumbled upon the answer in a slightly unrelated StackOverflow question about calling a trait from another trait with params in factory_girl.

Ultimately, you use the trait name from the first trait as a method invocation in the referencing trait:

FactoryBot.define do
  factory :user do
    role
    trait :with_supervisor do
      # complex set up might go
      # here
      after(:create) do |user|
        supervisor { create(:user) }
      end
    end
    trait :with_organization do
      with_supervisor # invoke the other trait first
      organization
    end
  end
end

RAW_POST_DATA in rspec rails for Rails 5.2 and beyond

The last time I was trying to specify RAW_POST_DATA in rspec was probably Rails 3 or 4, but I ran into a situation trying to test an edge case for error handling where I wanted that same functionality. I quickly found this issue [Unable to POST raw request body], but didn’t immediately figure out what wasn’t being set correctly.

In this case the test setup I was using was setting multipart/form-data instead of application/xml on the content types:

{:HTTP_ACCEPT=>"application/xml", :HTTP_CONTENT_TYPE=>"multipart/form-data", :CONTENT_TYPE=>"multipart/form-data"}

Because of this, the Rails controller tests that rspec hooks into was trying to break following malformed xml down to parameters:

            <test>
              <data&nbsp;
              <![CDATA[THIS|IS|SENSITIVE|BUT|MALFORMED]]>
              </data>
            </test>
 Minitest::Assertion:
   Expected response to be a <400: bad_request>, but was a <422: Unprocessable Entity>
   Response body: <errors>
       <error>["<test>\n  <data", "nbsp;\n  <!"] are not permitted parameters</error>
   </errors>

I finally noticed that the mime-type might be involved. In this code, Content-Type was also an issue, so:

  • Removed HTTP_CONTENT_TYPE from the headers
  • Set CONTENT_TYPE header to 'application/xml' instead of 'multipart/form-data' to prevent automatic params parsing in this case.
  • Passed as: :xml into the test to get the 'mime-type' correct.

Ultimately, if your code hasn’t boxed you in, then the as: :xml and passing raw data as a parameter should work:

post things_path, params: raw_xml_data, headers: non_form_data_headers, as: :xml

## replacement for the following:
# @request.env['RAW_POST_DATA'] = raw_xml_data
# post things_path

NoMethodError undefined method `shared_examples_for’ for main:Object for bundle gem rspec

If you create a gem stub using bundle gem thing and select rspec as your test suite, you may get an error similar to the following:

❯ bundle exec rspec

An error occurred while loading ./spec/thing_spec.rb.
Failure/Error:
  shared_examples_for 'saying hello' do
    puts "hi"
  end

NoMethodError:
  undefined method `shared_examples_for' for main:Object
# ./spec/thing_spec.rb:1:in `<top (required)>'
No examples found.

whenever using describe, shared_examples, and shared_examples_for, etc…

After a lot of diving into rspec source code to verify where shared_examples_for was defined (rspec-core so…) I noticed the following code in the stubbed spec_helper.rb:

  # Disable RSpec exposing methods globally on `Module` and `main`
  config.disable_monkey_patching!

If you comment out config.disable_monkey_patching!, then those methods will be included at a top level.


rspec-rails tricks: Hacking Your Routes for a Spec

I don’t know that this is so much a reminder of “What can be done with rspec-rails” as a note that, “If you do this will rspec-rails, you will also need to undo it.”

Today’s note: If you hack the application routes for a controller test, you have to reload routes.

The original problem

This all came down to a spec that was attempting to test a method in ApplicationController. In our Rails 3 setup (rspec-core and rspec-rails 3.7.0), the tests only needed a monkey patched ApplicationController to happen prior to the test:

before do
  class ApplicationController
    def dummy_action
      dummy_method_actually_under_test
    end
  end
end

it 'example do test' do
  get :dummy_action #etc...
  # validate dummy_method_actually_under_test did the thing
end

The fix

In Rails 4 with the same gem versions, we’d end up with the “No route matches…” which could be remedied by redefining the routes.

before do
  class ApplicationController
    def dummy_action
      dummy_method_actually_under_test
    end
  end
  Rails.application.routes.draw do
    get 'application/dummy_action/:id', to: 'application#dummy_action'
  end
end

Great! The test passes now! (Insert philosophical argument about whether the person writing the test should have tested a method in this way.)

Or not…

..until you run the rest of the suite, of course. Now *every* subsequent controller test has a “No route matches” issue. (Insert philosophical argument about whether you should be writing controller tests.)

This should serve as a periodic reminder to clean up after your tests, which in this case is:

after do
  Rails.application.reload_routes!
end

Circular dependency detected while autoloading constant

I ended up with this error because the class name I was referencing was ClassNameXML but I was trying to call it as ClassNameXml.

Not following naming conventions combined with not calling a class by its properly capitalized name will trigger this. I burned an hour figuring this out. I hope you don’t have to.


rspec-rails 3 deprecation warnings scripts and fixes

Upon upgrading to rspec 2.99.0, I began to receive deprecation warnings for several things that had issues, and to do a few updates:

Because the project was using its syntax and stub_model I needed to add that functionality back into rspec in the :test group of my Gemfile:

  # Support for its syntax
  gem 'rspec-its', '~> 1.0.1’

  # Support for stubbing model in view specs:
  gem 'rspec-activemodel-mocks', '~> 1.0.1'

On top of that I had to do substitutions for be_true, be_false, and_return with block syntax, and change pending to skip for a few pending tests.

I was able to run the following find + sed commands for each of these (note that the .original part is Mac-specific):

find . -name '*.rb' -exec sed -i .original 's/be_true/be_truthy/g' {} +
find . -name '*.rb' -exec sed -i .original 's/be_false/be_falsey/g' {} +
find . -name '*.rb' -exec sed -i .original 's/and_return[ ]*{[ ]*\([^} ]*\)[ ]*}/and_return(\1)/g' {} +
find . -name '*.rb' -exec sed -i .original 's/^\([ ]*\)pending /skip /g' {} +

I haven’t completely foolproofed the last two regexes, so I’d make sure to have your work committed for each set of substitutions (and to make sure rspec still passes your tests, of course.)

And the cleanup:

find . -name '*original' -exec rm {} +

Lastly, to quiet the remaining deprecation warnings that I had, I added this to my spec_helper.rb:

Rspec.configure do |config|
# other config code
#
  config.mock_with :rspec do |mocks|
    mocks.yield_receiver_to_any_instance_implementation_blocks = true
  end

  config.infer_spec_type_from_file_location!
  config.raise_errors_for_deprecations!
#
# other config code
end

This configuration, as I understand it, will be standard with 3.0:

  mocks.yield_receiver_to_any_instance_implementation_blocks = true

This setting is required for 2.99 because you will unconditionally get a deprecation warning even if you set the type…

 
  config.infer_spec_type_from_file_location!

And I wanted to raise an error for any other deprecation warnings:

  config.raise_errors_for_deprecations!

After all the dust had settled from 2.99, I upgraded rspec-rails to 3.0.0 and did a bundle update:

  gem 'rspec-rails', '~> 3.0.0' # Test framework

I’ve unearthed a few deprecation warnings about :should syntax, which I expect to update later.

One misleading deprecation warning:

Requiring `rspec/autorun` when running RSpec via the `rspec` command is deprecated. Called from /Users/tpowell/.rbenv/versions/2.1.1/lib/ruby/gems/2.1.0/gems/activesupport-4.1.1/lib/active_support/dependencies.rb:247:in `require'.

…was actually due to having require 'rspec/autorun' in my spec_helper.rb


mkdir with intermediate directories and rails generate view spec

I’ve been running into a lot of cases where I need to build intermediate directories for a path. Apparently, the correct option is -p

mkdir -p dir/tree/to/make

From the mkdir man page:

     -p      Create intermediate directories as required.  If this option is not specified, the full path prefix of each operand must already exist.  On the
             other hand, with this option specified, no error will be reported if a directory given as an operand already exists.  Intermediate directories
             are created with permission bits of rwxrwxrwx (0777) as modified by the current umask, plus write and search permission for the owner.

I’ve assigned it an alias in my .zlogin:

alias mkdirtree='mkdir -p'

Of course, this all came about because I needed to an generate rspec file for a view, which can be done with:

rails g rspec:view name/of/controller action another_action

Results in the following files in the spec/name/of/controller directory:

action.html.erb_spec.rb
another_action.htl.erb_spec.rb

rspec before(:all) is tempting but stubs don’t play nicely with it.

I was looking at an rspec test with multiple expectations in the same example.

  before(:all) do
     SpecialClient.any_instance.stub(:login).and_return { true }
  end
#
# MUCH SPACE
#
# WOW
#

  it "does stuff successfully" do
    # preceding code, including controller action
    expect(result.valid?).to be_true
    expect(response.body).to match /All is happy/
    expect(response.code).to eq "200"
  end

I wanted to add an additional test, but didn’t want it to be lumped in the “does stuff successfully” example, so I went ahead and refactored the expectations into different examples, and ran into:

NoMethodError:
       undefined method `any_instance_recorder_for" for nil:NilClass

I found this answer on StackOverflow that helped me track down that the use of a before(:all) vs. before(:each) might be the problem.