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:

Copy
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:

Copy
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

Copy
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

Copy
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

Copy
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.

More great blog posts from Antony Siegert

  • Integrating Mailchimp with Scrivito

    There are a myriad of ways to collect email addresses to communicate with your audience. Mailchimp is one of those, and, if you are not familiar with it, you should check them out. Mailchimp is an all-in-one marketing platform that provides quite a lot of features. The most basic feature is...

  • Expanding Scrivito’s Pricing Options

    Announcing new pricing options for Scrivito As we expand the feature set of Scrivito, we also have to determine the most appropriate pricing model for our customers. We have always considered pricing to be an important factor, but the fact remains that the product needs to grow, and requests from...

  • Example App Updates

    What is new in the Example App? The Example App has been a big part of the Scrivito journey, not only for you – our customers – but also for us as developers. We developed the Example App to showcase some of what could be done when implementing a Scrivito-based app. It is used, every day, to test...

  • Customizable Page Menus

    Scrivito’s top-right menu is customizable! Menus can make editors’ lives much easier, and we have had several requests to add items to the menu to aid in a more efficient content generation. So, our developers worked hard and took our mantra of complete developer flexibility to heart. Beginning...

  • The Scrivito Example App now Uses Bootstrap 4

    With Bootstrap 4 finally being released to the wild and with all the new features, of course, we wanted the Scrivito Example App to have the latest and greatest. After all, the Scrivito Example App is our showcase for implementing the Scrivito CMS. While the change from Bootstrap 3 to Bootstrap 4...

  • A/B Testing with Scrivito and Google Optimize

    Fine tuning a website takes time, and there are many options to consider. But when it really comes down to it, it's important to find out if the visitors are happy with the improvements. To do this, we need to compare their reactions to the present and the improved versions of the pages...

  • How to Create a Blog Post in the Example App

    Creating a blog post is simple with the Example App. A blog post has an author, so we will want to add our author page, too. Every blog post page consists of a title, a subtitle, and a widgetlist. The widgetlist is where you will build up the body of the blog post using the available widgets in...