No Internet Means Better Test Isolation


One day I found myself riding in a car with my family and it was a long road trip, with little to no internet and realized that my tests required internet connectivity. I was also heavily dependent upon code examples that I could find.

I’ve been absolutely horrible with testing my code. I would write code, test it out in the ruby console and then push it live. It felt like I was moving fast. I was being agile. Wrong! Testing is necessary in order to succeed. Often I would push code and do the good ole’ cowboy code em’ up. This is a horrible idea, don’t do that.

Stubs And Mocks

I’ve seen arguments for and against the use of stubs and mocks. I like mocks and stubs. I do think they are useful, and they have their place. Many people find themselves over stubbing and over mocking. This can lead to brittle tests.

I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail. Abraham H. Maslow

Corey Haines has a good presentation called Yay Mocks!. Definitely worth the time to watch.

Using stubs and mocks in a restrained fashion will result in maximum payoff. I attempted to stub and mock everything in a controller unit test. This turned out to be a hellish nightmare to maintain. I did discover some cool logical control flow that I could utilize. I could force CanCan to raise exceptions and cause some fun errors in the controller.

# spec/controllers/threads_controller.rb
describe ThreadsController do

  describe '#create' do
    context 'when a user is signed in' do
      before { sign_in user }
      context 'and is authorized' do
        before { controller.stub(:authorize!).and_return(true) }
        context 'and the thread is created' do
          before do
            post :create, thread: {title: 'Some Title'}
          end
          it { should respond_with 302 }
          it { should set_the_flash[:success] }
          it 'should create the thread' do
            expect(Thread.count).to eq(1)
          end
        end

        context 'and the thread is not created' do
          before do
            Thread.any_instance.stub(:valid?).and_return(false)
            post :create, thread: {title: 'Some Title'}
          end
          it { should respond_with 200 }
          it { should set_the_flash[:error] }
          it 'should not create a thread' do
            expect(Thread.count).to eq(0)
          end
        end
      end

      context 'and is not authorized' do
        before do
          controller.stub(:authorize!).and_raise(CanCan::AccessDenied)
          post :create, thread: {title: 'Some Title'}
        end
        it { should respond_with 403 }
        it { should set_the_flash[:error] }
        it 'should not create a thread' do
          expect(Thread.count).to eq(0)
        end
      end
    end

    context 'when a user is not signed in' do
      before { post :create, thread: {title: 'Some Title'} }
      it { should respond_with 403 }
    end
  end

end

Testing permissions should not apply to controller tests. This ia a boundary and should be stubbed. You can unit test your permissions in another set of tests, but they do not belong in the controller test.

Boundaries

I strongly urge you to watch Boundaries by Gary Bernhardt. Understanding where an object’s boundaries lie, is key for test isolation. Ruby has a nice open class principle that allows for stubing and mocking. Earlier I pointed out that in a controller permissions were considered a boundary.

No Internet

Now for the fun story. I had no internet on my laptop while we traveled down the highway. I was writing code and then realized I should run my tests real quick to ensure I didn’t cause a massive problem. What I found out to my surprise is that more than half of our test suite failed, due to the need to talk to a 3rd party API. Ooops.

All I had was my phone and even then all I had was spotty coverage. I remembered seeing the two videos mentioned above, and thought to myself, “This is similar to what TDD is right?” I was right, as I went through and looked at why the tests were failing, I noticed that they were failing in places that were difficult to test. On top of the areas difficult to test, it was often over stepping boundaries.

I used my phone to look up RSpec Mocks and tried out a few examples to make sure I understood what was going on.

foo = double('User')
foo.stub(:update_attributes).and_return(true)

it 'should return true' do
  expect(foo.update_attributes({:garbage => 'in'})).to eq(true)
end

Revelation

And when the test greened up, a sudden grin appeared on my face. I realized that I could do anything. I have never once been happy about testing in Java or PHP and I couldn’t help but be really excited when I started seeing some tests come up green.

I started to stub and mock places where I was making calls to an API. Many of these tests, I simulated errors that could be encountered while interacting with the API.

I realized that there were parts of our application that were extremely difficult to isolate. This was indicative of “code smell”. I wrote some tests on how I wanted the peices to interact, then made the peices interact to make the tests green.

Being able to simulate errors from the API was a HUGE help. It allowed me to see potential errors and handle them appropriately rather than show a big ole’ 500 error page to our customer.

Resources

Here is a list of resources that I utilize all the time. In fact, they are bookmarked and I often visit them.