Measurable Success «10 Checkpoints for Future-Proof Enterprise CMS» White Paper
Measurable Success - White Paper

Testing Your Scrivito-Based Rails App 

The question of how to test Scrivito-based apps comes up less often than you would expect, but none the less it is asked. Remember that Scrivito is just Ruby, but being mainly an API, there are some caveats. It is best kept in mind what Scrivito is, a Content Management System, so your testing will essentially revolve around views.

To start, the Scrivito SDK is fully tested. So, as usual, there’s no need to test the API.

TL;DR

For model/unit tests it is recommended to limit testing to the model attributes.

Your views and content are best tested with either JavaScript or more as a full integration test. We recommend to set up a separate CMS and test capybara style with a web-driver of some sort.

Pages and widgets themselves are tested like normal Rails models. Actually, there is no need to test stuff coming from the CMS. We recommend to stub the CMS attribute methods and only test your custom methods as one would normally do with a standard Rails model.

Model specs

Here is a sample model spec based on Scrivito’s example application:

require 'rails_helper'

RSpec.describe BlogPostPage, type: :model do
  it { is_expected.to respond_to(:abstract) }
  it { is_expected.to respond_to(:author_email) }
  it { is_expected.to respond_to(:author_name) }
  it { is_expected.to respond_to(:image) }
  it { is_expected.to respond_to(:published_at) }

  describe :text_extract do
    it "returns an abstract when one is provided" do
      short_abstract = "this is a short abstract of the blog post"
      blog = BlogPostPage.new
      allow(blog).to receive(:abstract).and_return(short_abstract)

      expect(blog.text_extract).to eq(short_abstract)
    end
  end
end

Here is a little more complex model spec that additionally tests nested widgets:

require 'rails_helper'

RSpec.describe PanelGroupWidget, type: :model do
  it { is_expected.to respond_to(:layout_type) }
  it { is_expected.to respond_to(:panels) }

  describe :text_extract do
    it "returns text from each widget" do
      text = "this is the text in the text widget"
      text_widget = TextWidget.new
      allow(text_widget).to receive(:text).and_return(text)
      widget = PanelWidget.new
      allow(widget).to receive(:content).and_return([text_widget])
      group = PanelGroupWidget.new
      allow(group).to receive(:panels).and_return([widget])

      expect(group.text_extract).to eq([[text]])
    end

    it "returns deliminated text when there are multiple texts" do
      text1 = "this is the text in the first text widget"
      text2 = "two is the text in the second text widget"
      text_widget1, text_widget2 = TextWidget.new, TextWidget.new
      allow(text_widget1).to receive(:text).and_return(text1)
      allow(text_widget2).to receive(:text).and_return(text2)
      widget = PanelWidget.new
      allow(widget).to receive(:content).and_return([text_widget1, text_widget2])
      group = PanelGroupWidget.new
      allow(group).to receive(:panels).and_return([widget])

      expect(group.text_extract).to eq([[text1, text2]])
    end
  end
end

Controller specs

Here you can find a few example controller specs and a little spec helper for mocking pages and widgets. Scrivito provides a helper method, for_scrivito_obj, as some controllers do not have explicit routes by default. Changes in Rails 5 render the controller specs optional. These examples don’t really test anything but that “it works”. Nevertheless, they are included here to show some available patterns.

Example spec_helper for mocking

module ScrivitoSpecHelpers

  def mock_obj(klass, attributes={})
    obj = klass.new

    attributes.each do |name, value|
      allow(obj).to receive(name) { value }
    end

    obj
  end

  def mock_widget(klass, attributes={})
    widget = klass.new

    attributes.each do |name, value|
      allow(widget).to receive(name) { value }
    end

    widget
  end

end

Example controller test with mocking

require 'rails_helper'

RSpec.describe BlogPostPageController, type: :controller do
  let(:obj) { mock_obj(BlogPostPage, published_at: Time.current - 1.year) }

  describe 'GET index' do
    it 'renders the blog post page' do
      request.for_scrivito_obj(obj)

      get :index

      expect(response.status).to eq(200)
    end
  end
end

Integration tests

For the integration tests, it would be best to mock your data requests and test only the code of your app. In an effort to keep it simple, and since one can start up the example app with consistent sample data, we are illustrating a request spec without mocking. The following example is testing that the content is rendered as expected, based on the parameters set in the controller.

Request specs

require 'rails_helper'

RSpec.describe "BlogPage", type: :request do
  describe "GET /blog" do
    before do
      get "/blog"
      @posts = BlogPostPage.all.order(published_at: :desc).to_a
    end
    it "renders the blog overview page" do
      expect(response).to have_http_status(200)

      expect(response.body).to include "Tags"
      expect(response.body).to include "Feed"
      expect(response.body).to include "Photo Stream"
      assert_select "a[href=?]", "/#{@posts[0].title.parameterize}-#{@posts[0].id}"
    end

    it "shows titles of only the last 3 recent posts" do
      assert_select "div.col-md-9" do
        assert_select "div.row", 3 do
          assert_select "h4", @posts[0].title
          assert_select "h4", @posts[1].title
          assert_select "h4", @posts[2].title
        end
      end
    end

    it "does not include titles of more than the last 3 recent posts" do
      assert_select "h4", count: 0, html: @posts[3].title
    end

    it "includes images to the last 5 recent posts in the photo stream" do
      assert_select "div.col-md-3" do
        assert_select "div.panel" do
          assert_select "h2.panel-title", "Photo Stream"
          assert_select "ul.list-inline li a img", 5
          assert_select "a[href=?]", "/#{@posts[0].title.parameterize}-#{@posts[0].id}"
          assert_select "a[href=?]", "/#{@posts[1].title.parameterize}-#{@posts[1].id}"
          assert_select "a[href=?]", "/#{@posts[2].title.parameterize}-#{@posts[2].id}"
          assert_select "a[href=?]", "/#{@posts[3].title.parameterize}-#{@posts[3].id}"
          assert_select "a[href=?]", "/#{@posts[4].title.parameterize}-#{@posts[4].id}"
        end
      end
    end

    it "has pagination" do
      assert_select ".previous", "← Older"
      expect(response.body).to_not include "Newer"
    end
  end
end

This code tests the blog overview page. It checks whether it’s returning the right blog posts and page elements, based on the parameters set in the controller.

If you cannot mock or do not wish to, it is recommended to set up additional Scrivito CMS instances to be used as testing environments. They would be used to create a working copy, add the content, publish and eventually delete everything with each run of the test. Keep in mind, an instance would only be able to deal with a single run at a time. So, if a developer wants to run such a test locally, they would have to have their own CMS instance. Otherwise, if there’s another developer running these tests in parallel against the same CMS instance, they will override each other’s data, resulting in non-predictable errors. Also, if there are CI machines running integration tests with publishes, each of these machines needs its own erasable CMS instance.

Creating the test tenant content is best done either in the test or via seed files. We advise to not copy anything from the production instance and in general completely isolate these two data sources in order to keep the production tenant as far from danger zones as possible. Also, in order to keep the tests as fast as possible, the seed data should to be as tiny as possible.

At Infopark, we use the following test workflow:

  • Reset a CMS instance at the very beginning of the test suite.
  • Run tests making publishes.

Note, we do not reset the tenant on each run, since it’s a relatively expensive and long-running operation.

In a nutshell …

… you have seen some ways to test your Rails app when used with Scrivito. Test your app and code as you would normally, and when testing content, test the view.