1
votes

I'm just getting started with Rails and I'm writing some unit tests for a Todo list app with RSpec. I've got the REST API written and working, but now I can't seem to figure out an issue with a test. Here's the spec:

require 'rails_helper'

describe "Lists API" do
  context "from start" do
    it 'is empty' do
      get "/lists"
      expect(response).to be_success
      expect(json.size).to eq(0) 
    end

    it 'can create Lists' do  
      post "/lists", :list => {:title => "First List", :status => "Unstarted"}
      expect(response).to be_success

      post "/lists", :list => {:title => "Second List", :status => "Unstarted"}
      expect(response).to be_success

      #lines only here to show the problem only exists in a different test block
      get "/lists"
      expect(response).to be_success
      expect(json.size).to eq(2) 
    end

  end

  context "once populated" do
    it 'can view created lists' do
      get "/lists"
      expect(response).to be_success
      expect(json.size).to eq(2)
    end
  end


end

Then when I run RSpec, I get this error:

Failures:

  1) Lists API once populated can view created lists
     Failure/Error: expect(json.size).to eq(2)

       expected: 2
            got: 0

       (compared using ==)

It seems like the database is getting emptied for each it block. Is that correct? Is there any way to have a fresh database for each describe but not have it emptied for each it?

2

2 Answers

0
votes

Yes, rspec will treat each example (it block) independently.

One thing you may want to consider is breaking up your spec by action and endpoint, something like:

describe 'Lists API' do
  describe 'GET lists' do
    context 'when lists empty' do
      before(:each) do
        get '/lists'
      end

      it 'responds with success'
      it 'json response is empty'
    end

    context 'when lists present' do
      let!(:list) { List.create(title: 'First List', status: 'Unstarted') }

      before(:each) do
        get '/lists'
      end

      it 'responds with success'
      it 'json response is present'
    end
  end

  describe 'POST lists' do
    it 'can create lists'
  end
end

This way, you can better isolate and organize what you are testing. Really, what you want to test is that you can successfully create a list and successfully get/render a list (both empty and with list items).

0
votes

If you want to test your controller, you'd be better served by writing it blocks that are independent of each other.

Why: Saving objects between blocks opens you to the problem of posting twice to /lists and having one post fail. In that case your POST it block would fail AND your GET it block would fail because the GET is relying on the POST to be successful. This is confusing because there could be nothing wrong with your GET action, but it's test would be failing anyway.

Improvement: Set up isolated tests for each action in the controller like:

describe 'GET /lists' do
  before do
    List.create(title: 'first list', status: 'Unstarted')
    List.create(title: 'second list', status: 'Unstarted')
  end

  it 'renders all lists' do
    get '/lists'

    expect(response).to be_success
    expect(json.length).to eq(2)
  end
end

(which uses before to create the two fetched records) and

describe 'POST /lists' do
  it 'can create Lists' do  
    post "/lists", :list => {:title => "First List", :status => "Unstarted"}

    expect(response).to be_success
    expect(List.count).to eq(1)
  end
end

This way if either the GET or the POST are broken, you'll know which is really causing the problem.

Finally, if you still want to test a more realistic user flow, consider writing one large integration test. Here's the Rails docs integration info and RSpec's controller integration info.