Showing posts with label rspec. Show all posts
Showing posts with label rspec. Show all posts

Monday, February 15, 2016

Rspec tricks for DRY mocked specs

These “tricks” are straight from the docs. One will help DRY up your specs, the other will help you mock your controller for controller tests.

Shared Contexts

Shared context allows for extracting certain setup steps and sharing them across several different sets of tests.

First, define your shared context:

RSpec.shared_context "user setup" do
  let(:user) { User.new(name: 'Chuck Norris') }
  before { allow(user).to receive(:some_method) }
end

Then, include it in your spec files:

describe UsersController do
  include_context 'user setup'
  
  # Tests go here...
end

Anonymous Controllers

Anonymous controllers are a way to specify certain behavior for the controller you are trying to test, or more likely for a parent controller. The thing I found it most useful for, and not coincidentally the thing that the docs says it is useful for, is testing global error handling.

describe UsersController do
  controller do
    def index
      raise AccessDenied
    end
  end

  subject { get :index }

  describe "handling AccessDenied exceptions" do
    it "redirects to the /401.html page" do
      expect(subject).to redirect_to("/401.html")
    end
  end
end

Monday, October 26, 2015

Debugging Capybara Poltergeist tests in Rails

I made the switch from selenium webdriver to poltergeist on my capybara tests when the suite started taking 20 minutes to run. (For my process of getting the test suite under control, check out my previous post.) While poltergeist has definitely been a boon, it can sometimes be hard to debug because I can’t physically see the page. That’s where this tip comes in handy, stolen from the Quickleft blog:

Find the Port Address and Pause the Test 
Sometimes I just want to see what's going on in the browser. Although save_and_open_page gives you some idea of what's going on, you can't really click around, because the page it gives you is static and lacking assets. To dig into what's going on, I like to use a trick I learned from my mentor, Mike Pack. 
Just above the broken line in your test, add these two lines of code. Note that you have to have pry installed in your current gemset or specified in the Gemfile for this to work. 
puts current_url
require 'pry'; binding.pry 
Run the specs, and when they pause, copy the url and port number from the test output. 
Open your browser and paste the address into the window. Voila! You're now browsing your site in test mode!

Thanks again to the Quickleft folks for this cool tip - it’s one of five tricks for less painful testing.

Tuesday, September 1, 2015

More readable value change testing in rspec

Some simple but effective rspec syntax that I just learned:

expect{Object.method}.to change{Object.count}.from(0).to(1)

I think this is fairly straightforward, but essentially it just lets you test for value changes. This is basically just a clearer way of writing expectations and would replace something like this:

expect(Object.count).to eq(0)
Object.method
expect(Object.count).to eq(1)

Check the rspec docs for more details.

Wednesday, June 24, 2015

Take control of your rspec test suite, Part 1

Let me tell you a little story about how I took our test suite from 25 minutes run time to just under 3 minutes while still testing all the same functionality.

A large application can generate a very unwieldy test suite. On one project I worked on, the test suite took 45 minutes to run on our CI, and when I tried to run the whole thing locally, I stopped it at the 1 1/2 hour mark when it looked like it still had a ways to go.

I can live with a suite like that for awhile, because I just run the tests that are specific to the work I’m doing. However, there are times when I’m working on something that could potentially affect several different places in the app, and I may not know exactly where those places are, so I have to run the whole suite. Waiting 45 minutes to be able to finish my feature is NOT cool, especially since that will likely have to happened a few times to ensure all tests are fixed.

On that project, I was limited with what I could do on the test suite due to budget constraints. However, on another project I worked on, our test suite took 25 minutes to run, but the project manager saw the financial benefit of trimming this number down and so let me spend some time fixing our tests.

I’ll be writing a few different posts on this topic. Step 1 is the easiest part: gathering data. Often, only a handful of tests take the longest to run, so we want to find out which of our tests are “losers” and try to refactor them into “winners.”

Thankfully, rspec gives us a command to do just that:

rspec --profile
# or rspec -p

This will run the entire suite, then list out the 10 examples that took the longest to run. If you want more than 10, you can specify the number you want as an argument, i.e. rspec -p 25.

You can also use this when running on certain test groups. So, if you wanted to find out the 17 slowest feature specs:

rspec -p 17 spec/features

Now you know the worst offenders. Stay tuned for part 2 where we delve into how to fix them up.

Thursday, June 18, 2015

Hack your Rspec setup using tags

I ran into an issue in rspec the other day where I had a before block to setup about 10 different tests, but I needed to write one more that was similar enough that it belonged in the same context but it required slightly different setup so the before block I already defined.

What to do? I could break out the existing tests into a nested context block and use the setup there, then do another nexted context block for my new test, but I really didn’t want to do that because all these tests should belong in the same context. I just wanted to skip the before block on one specific test.

Rspec tags to the rescue!

In this case, my new test was a JS test and all others were not (which is partly why the setup was different. So, the test was already tagged js: true, and in mybefore` block, I modified it like so:

before(:each) do |test|
  # Run this only on tests NOT tagged with js: true
  if test.example.metadata[:js] == nil
    # Existing setup code
  end
end

Works like a charm, and I still have all my related tests within the same context like I wanted.

Hat Tip: http://stackoverflow.com/questions/27864080/how-can-i-skip-some-setup-for-specific-rspec-tags