RarestBlog

Testing PHP With Rspec and Capybara

I’ve had a long day yesterday debugging our tests. Some things just get so boring and hard to dig into.

Like I’ve written earlier - we have our own system to test the website, which looks something like this inside of PHPUnit:

function testSomething() {
  $this->get('/test');
  $this->assertContains('Test Item');
  $this->post('/test', array('id' => '13'));
  ....
}

which is okay for most situations, but still it’s “developed here”.

I’m starting to have “developed here” syndrome. Contrary to popular “developed not here” syndrome (which is when you refuse any software that you haven’t developed), I’m starting to dislike things I write and put more trust in Open Source libraries. Though for now PHP libraries disappoint me more than make me happy. But I like Ruby libraries (“gems”) a lot.

So, I’ve taken a look at Capybara - a cool library that does something like this and you can use it to test PHP websites:

feature "Store Selection" do
  it 'should redirect to store selection' do
    visit '/'
    page.current_path.should == '/store-selection/'
    click_link 'Stockholm 1'
    page.should have_content('+43 999 999999')
  end


  # this checks that when visitor goes to main page
  # the number of rows in database table `page_views`
  # is increased by 1

  it 'should increase number of views' do
    lambda {
      visit '/'
    }.should change(PageView, :count).by(1)
  end
end

Cool, huh? Follow along and I’ll tell you more.

Codeception

There are PHP libraries like Codeception, which give you similar stuff, but personally I like Capybara more and let me convince you.

Ruby, RVM, Capybara, Rspec

First of all you need to install Ruby and RVM.

On Ubuntu derivatives it’s easy (and you should use Ubuntu-like system for development, personally I use Mint, if you want to know how to use it in Windows in VirtualBox - try this or this, then after login - click on ‘Menu’ at bottom and type “terminal” and click on terminal, then copy those commands there):

In Terminal:

sudo apt-get install curl 
curl -L get.rvm.io | bash -s stable
rvm requirements
# check for 'apt-get install' line and run it
sudo /usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion pkg-config

Then you either need to restart your terminal or run the source line that rvm recommends.

In Terminal:

rvm install 1.9.3
rvm use 1.9.3 --default

Last line is optional, but makes your life easier (makes your new Ruby default).

Make sure ruby is working by running

In Terminal:

ruby -v

You should see version 1.9.3.

Gems

Next, create a directory for your tests and we need a file called Gemfile:

source 'https://rubygems.org'

gem 'capybara-mechanize'
gem 'capybara'
gem 'rspec'
gem 'activerecord'
gem 'mysql2', '> 0.3'
gem 'pry'

These are some very useful libraries that you are going to need.

If you don’t need mysql - remove the corresponding line.

Then you need to install libmysql2client-dev:

In Terminal:

sudo apt-get install libmysqlclient-dev

Now we are ready to install all the Ruby “gems” that we need. Go to where your Gemfile is and run:

In Terminal:

bundle install

Preparing for tests

Create a spec file like this:

Dir["test_*.rb"].each {|f| require_relative f}

Now you can create a spec_helper.rb file:

# encoding: utf-8

require 'capybara/mechanize'
require 'capybara/rspec'
require 'active_record'
require 'pry'
require './models'

ActiveRecord::Base.establish_connection({
  :adapter  => :mysql2,
  :host => "localhost",
  :username => "root",
  :password => '123',
  :database => "mediazone"
})

include Capybara::DSL
Capybara.javascript_driver = Capybara.default_driver = :mechanize
Capybara.app_host = 'http://my.development.server.dev'
Capybara.run_server = false

Replace http://my.development.server.dev with address of your host.

Creating the first test

Cool! Now create a actual test, like test_basic.rb:

# encoding: utf-8

require './spec_helper.rb'

feature 'Main Page' do
  it 'shows the main page content' do
    visit '/'
    page.current_path.should == '/'
    page.should have_content('Main Page!')
  end
end

Create an empty models.rb file for now.

You should also understand that this will try to download the http://my.development.server.dev/ defined in Capybara.app_host above and it should have the content of Main Page!.

Running the test

You are ready to test something, run this command:

In Terminal:

rspec

Yay! It runs the tests!

Database (MySQL)

Let’s add a model to models.rb:

class Shop < ActiveRecord::Base
end

This creates a model Shop, which is a MySQL table shops, you can change it like this:

class Shop < ActiveRecord::Base
  self.table_name = "shop_table"
end

Let’s try to do something in tests and make sure it works, change test_basic.rb to this:

# encoding: utf-8

require './spec_helper.rb'

feature 'Main Page' do
  it 'shows the main page content' do
    visit '/'
    page.current_path.should == '/'
    page.should have_content('Main Page!')
  end

  it 'shows the list of shops' do
    Shop.destroy_all
    visit '/'
    page.should_not have_content('Shop 1')

    Shop.create!(title: 'Shop 1')
    visit '/'
    page.should have_content('Shop 1')
  end
end

Running only one test

Again you can run rspec or this:

In Terminal:

rspec -e 'list of shops'

This will filter out the tests so that only ones containing list of shops will be run.

Well, this works of course only if you make your main page show list of shops.

More fun stuff

Some more fun stuff to try in tests:

it 'should create shop'
  visit '/admin/create_shop'
  fill_in 'title', with: 'Shop 2'

  lambda {
    click_button 'Create Shop'
    # or like this: find('.shop-button').click
  }.should change(Shop, :count).by(1)

end

So, it makes sure that when user clicks ‘Create Shop’ it actually creates a shop and count of shops (which you can get as Shop.count gets increased by exactly 1).

Even more stuff

it 'should change title'
  Shop.where(title: 'Shop 1').update_all(title: 'Shop 2')
  visit '/'
  page.should have_content('Shop 2')
end

The Shop.where... stuff is called Ruby's ActiveRecord (active_record gem to be precise). It’s super awesome, like you can do this:

class Shop < ActiveRecord::Base
  has_many :items  ## the table `items` should have a field `shop_id` for this to work
end

class Item < ActiveRecord::Base
end

Shop.where(title: "Shop 1").first.items.where('price > ?', 100).first(10).map(&:title)

to get flat array of titles of all items in “Shop 1” which cost more than 100 something :)

A few hints

If you need to test SOAP services use savon gem, if you need to test APIs - use HTTParty gem.

Debugging

Also we’ve included cool pry gem. It’s a debugging console.

Like if you have a test that fails - you can always go to console that has the actual state, like so:

it 'should change title'
  Shop.where(title: 'Shop 1').update_all(title: 'Shop 2')
  visit '/'

  binding.pry

  page.should have_content('Shop 2')
end

Note the binding.pry line. It will stop the script at that line and you will be able to run commands in command-line more (REPL):

page.html
page.html =~ /Shop 2/
Shop.first
Shop.delete_all
visit '/'
Shop.where(title: "Shop 1").first.items.where('price > ?', 100).first(10).map(&:title).sort
.... etc

PHPUnit integration

Although I haven’t found a way (yet) to tightly integrate PHPUnit and Capybara, there is one idea that I currently try:

public function testRunRspec() {
    $microtime = microtime(true);
    $filename = '/tmp/rspec-log-'.$microtime.'.txt';
    file_put_contents($filename, '');
    $dir = getcwd();
    chdir(__DIR__ . '/rspec');
    exec('bash -lc "rspec > $filename"', $output, $exitCode);
    error_log(file_get_contents('/tmp/log.txt'));
    chdir($dir);
    $this->assertEquals(0, $exitCode);
}

It’s ugly and doesn’t work fully yet, but it let’s you keep your Continious Testing system, built on PHPUnit and still have Capybara.