Outline

You should remove child node if parent node is delete in mongodb. Here is simple example.

Models

Parent

class Parent
  include Mongoid::Document

  field :name,  type: String
  has_many :children
end

Child

class Child
  include Mongoid::Document

  field :name,  type: String
  belongs_to :parent
end

Example

Bad

$ rails c
$ parent = Parent.create(name: "parent1")
$ parent.children.create(name: "child1")
$ child = parent.children.first

# if you delete parent child1 still exist in db.
$ parent.destroy
$ Parent.where(id, parent.id).size # result is 0
$ Child.where(id, child.id).size   # result is 1

Solution

You have to add cleanup logic using “before_destroy” callback

Parent

class Parent
  include Mongoid::Document

  field :name,  type: String
  has_many :children

  before_destroy do
    children.destroy_all
  end
end

Example

Good

$ rails c
$ parent = Parent.create(name: "parent1")
$ parent.children.create(name: "child1")
$ child = parent.children.first

# if you delete parent child1 is delete too in db.
$ parent.destroy
$ Parent.where(id, parent.id).size # result is 0
$ Child.where(id, child.id).size   # result is 0

context "subdomain test" do
  it "should return subdomain" do
    request.host = "subdomain." + request.host
    expect(get_subdomain).to eq("subdomain")
  end
end

Capybara don’t find invisible element.

page.find("#delete-button")

In this case if #delete-button element has display: hidden or any other invisible style (e.g. visibility: none) ElementNotFound error is occurred.

Solution

You want to find it you should add visible option.

page.find("#delete-button", :visible => :all)

visible (Boolean, Symbol)

  • true - only finds visible elements.
  • false - finds invisible and visible elements.
  • :all - same as false; finds visible and invisible elements.
  • :hidden - only finds invisible elements.
  • :visible - same as true; only finds visible elements.

About Capybara-webkit

Capybara-webkit is a headless browser. It can execute javascript. (capybara can’t execute javascript.) If you want to do integration test you had better use capybara-webkit instead of capybara.

Prepare Gems

group :test do
  gem 'factory_girl_rails'
  gem 'capybara'
  gem 'capybara-webkit'   # add capybara-webkit
  gem 'database_cleaner'  # add database cleaner
end

You need to add database_cleaner. It makes database clean every test case.

Database clear vs Shared Connection

http://robots.thoughtbot.com/ said that…

When running your tests by default, Rails wraps each scenario in a database transaction. This means, at the end of each test, Rails will rollback any changes to the database that happened within that spec. Unfortunately, when we use a JavaScript driver, the test is run in another thread. This means it does not share a connection to the database.

There are 2 ways to use same data between capybara and capybara-webkit threads.

  • one is that commit all data and use it between 2 threads. after each test all of data is deleted.
  • another is taht share the database connection between 2 threads.

http://robots.thoughtbot.com/ recommended former (commit and delete data). If you want to know why ? You should read this article.

Change config

spec/rails_helper.rb

  • set Capybara.javascript_driver.
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require 'spec_helper'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
Capybara.javascript_driver = :webkit # add this line.
  • comment in to load spec/support/**/*.rb files.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }  # comment in this line.

# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.maintain_test_schema!
  • set use_transactional_fixtures to commit all queries in testcase. It makes capybara and capybara-webkit use same data.
  • If you want to know detail you should check this article.
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = false # set false

Add Helper

  • add this file as spec/support/database_cleaner.rb to clear database every test.
  • add this WaitForAjax module as spec/support/wait_for_ajax.rb to wait ajax request callback.

Use capybara-webkit

  • add spec file as /spec/features/**.rb
  • add js: true option to scenario function
feature "new todo title is inputed" do
    scenario "new todo should create in html" , js:true do
      visit "/"
      ....
      ....
    end
  end

Ajax test

  • add wait_for_ajax function after ajax trigger(e.g. click)
feature "todo's all-completed checkbox is clicked" do
    scenario "all todos should be deleted in html", js: true do
      visit "/"
      click_button("delete button") # request delete to server using ajax
      wait_for_ajax
      expect().to be XXXX
    end
  end

Sample

I created integration test useing capybara-webkit.

kashiro/todomvc_on_rails_fork (feature/update branch)

  • clone and checkout
git clone git@github.com:kashiro/todomvc_on_rails_fork.git
cd todomvc_on_rails_fork
git checkout feature/update
  • add qt for capybara-webkit
brew install qt
  • update gems
bundle install
  • execute test
rspec spec

Reference

Motivation

It is waste of time to fix the value of breakpoint for responsive design if you have that value in css and javascript. In the following case you have to fix both 400 and 400px.

ccs

@media screen and (min-width: 400px){
  body:after{
    content: 'desktop';
    display: none;
  }
}

Javascript

// ....
// ....
var minWidth = 400;

function isSp(){
   return !(minWidth < window.innerWidth);
}

using window.getComputedStyle

css

@media screen and (min-width: 400px){
  body:after{
    content: 'desktop';
    display: none;
  }
}

javascript

// desktop or ''
var deviceWidth = window.getComputedStyle(document.body,':after').getPropertyValue('content');

function isSp(){
  return deviceWidth !== 'desktop';
}

Deomo

http://codepen.io/Tkashiro/full/zxqwBK