Setting up test data for full-stack testing of a single page web application and its back-end

269 views Asked by At

Short version of my question:

In Cucumber tests written for an Angular single page web application, how do I accomplish the tasks usually performed in the "given" section of a scenario (such as setting up test data, defining database record associations, and ensuring a clean database state between tests) when testing the full-stack, both the front-end application and its back-end? The application source code is stored in two separate Git repositories, one for the front-end application and one for the back-end. The back-end is written in Ruby using the Rails API gem. The challenge in testing this application full-stack comes from the fact that it's actually two applications, in contrast with a more traditional Ruby on Rails application not implemented as a single page application.

Full version of my question:

I'm looking to write a series of Cucumber tests for a web application. The application consists of a front-end single page application written in Angular, and a back-end API written using Rails API. The source code for the front-end and the source code for the back-end each reside in their own Git repositories, providing a clean separation between the two codebases. Furthermore, the application uses MySQL and Elasticsearch.

I've used Cucumber in the past on previous Ruby on Rails projects. These projects were not developed as single page applications. In these projects it was easy to create Ruby objects as test data in Cucumber step definitions. For example, consider the following feature file from a Rails project that was not a single page application:

# features/home_page.feature
Feature: Home page

  Scenario: Viewing application's home page
    Given there's a post titled "My first" with "Hello, BDD world!" content
    When I am on the homepage
    Then I should see the "My first" post

The steps in this feature file can be implemented with the following step definitions:

# features/step_definitions/home_page_steps.rb
Given(/^there's a post titled "(.*?)" with "(.*?)" content$/) do |title, content|
  @post = FactoryGirl.create(:post, title: title, content: content)
end

When(/^I am on the homepage$/) do
  visit root_path
end

Then(/^I should see the "(.*?)" post$/) do |title|
  @post = Post.find_by_title(title)

  page.should have_content(@post.title)summary of the question
  page.should have_content(@post.content)
end

In Ruby on Rails projects that were not developed as single page applications, testing tools can be included into the project as Ruby gems. For me, these tools would include:

group :test do
  gem 'shoulda-matchers'
  gem 'cucumber-rails', require: false
  gem 'database_cleaner'
  gem 'selenium-webdriver'
end

group :development, :test do
  gem 'factory_girl_rails'
end

As you can see, this includes Factory Girl, used for setting up Ruby objects as test data and defining database record associations, and Database Cleaner, used to ensure a clean database state between tests. The inclusion of Selenium WebDriver is required for Cucumber scenarios which use JavaScript.

The situation is different in the case of my single page application. As described above, the application is broken into two separate code bases, one for the Angular front-end single page application and the other for the Rails API back-end interface.

However, single page applications such as my project still have the same testing requirements as more traditional Rails applications not built as single page applications. It's necessary to test the application full-stack to ensure each component, both front and backend, work together as expected. There will need to be Cucumber steps defined which create the "given" preconditions prior to a test, and it will be necessary to ensure a clean database between tests.

How do I test with Cucumber an application such as this, one with two code bases, implemented as a single page application? There's a version of Cucumber available for JavaScript called CucumberJS. However, I don't know how to create fixtures, record associations, and to ensure a clean database between tests using CucumberJS. There's also a tool for testing JavaScript written with Angular called Protractor. I imagine this tool taking the place of Selenium WebDriver.

1

There are 1 answers

0
Dziamid On

Answer to the short version question.

The challenge in testing this application full-stack comes from the fact that it's actually two applications

What you describe is tipical to single page apps. I have a number of spa examples (covered with E2E) where the backend is a RESTful API in PHP, Dot.net or ever a cloud web-service (parse.com). To my point of view, it is best to treat such apps, to qoute you agin, as 2 different apps and write 2 different sets of test for each. I believe that is the only way to write stable, scalable and fast e2e tests.

Basically what we do is create a local non-persistent mock api (in javascript) especially for testing. Here's a list of requirements/guidelines for such an api:

  1. Provides mock data for the test suit (read - it is coupled with test suit)
  2. Covers just enough methods to make the tests pass.
  3. It is non-peristent (request parameters is all there is to make a response, though there can be exceptions to that). All persistance is on the client (localstorage, cookies). It is important, because simply be clearing client storage and refreshing the page - you get the fresh state of you app! Where you to test a monolith - you'd have to clear and repopulate the database, which is a hume waste of time and completely non-scalable.
  4. Embedded into the angular repository, it is a part of a testing suit and a development process.

How does that look in practice. Let's say we are testing a Login feature of the website and it has 3 scenarios:

var homepage = require('pages/homepage'); //page object for home page

describe('Authentication', function () {

  beforeEach(function () {
    browser.manage().deleteAllCookies(); //clear persistance
    browser.refresh(); //refresh page
    homepage.get(); //go to homepage
  });

  it('should show error when user enters wrong credentials', function () {
    homepage.signIn('admin', '123'); //submit invalid credentials 
    expect(browser.getCurrentUrl()).toMatch('/home$');    
    expect(homepage.getSignInForm()).toContain('wrong credentials');
  });

});

To satisfy these tests all we have to do is implement an api for the POST /login request:

router.get('/login', function (req, res) {
  var username = req.body.username, password = req.body.password;

  if (password !== 'admin') {
    res.status(404).json({"error": "wrong credentials"});
  } else {
    res.json(User(username}));
  }
});