Testing Web Software: Unit Testing Methods

There are different levels of software testing. Here, we’ll take a look at unit, integration, system, and acceptance testing to see the whole picture. And we’ll focus on unit testing to see why it’s so important and which unit testing frameworks you can use.   

Levels of testing and why we need them

1. Unit testing

What is unit testing? Put simply, unit testing is a method of testing the smallest pieces of code, typically individual functions, in isolation. These small pieces of code are called units. A unit can be a line of code, a class, or a method, for example. With unit testing, the smaller the parts of code you test, the better the results, as testing smaller units gives you a more detailed view of your code performance. Plus, tests run fast when dealing with small units.

2. Integration testing

While unit tests verify the behavior of isolated parts of your code, integration tests cover interactions between different parts of your application. This is the next level of software testing, as integration testing checks if different units of the software work together correctly in real life. Integration tests validate complex scenarios, so they may require more effort and additional resources, for example databases or web servers.

Unit and integration tests are a great combo as they ensure that every isolated unit works correctly and that multiple units work flawlessly and as expected when integrated.

[Levels of web software testing]

3. System testing

Once you’ve successfully completed integration testing, you can move on to system testing. This is the first level at which you test the system as a whole. System testing checks whether all components are integrated and work correctly. For system testing, it’s crucial to create an environment similar to the real-life environment in which the system will be deployed. System testing verifies that the system meets functional, technical, and business requirements.

4. Acceptance testing

Acceptance testing is performed after unit, integration, and system testing. At this stage, the quality assurance team tests the application for quality by applying predefined test scenarios and test cases. The purpose of acceptance testing is to evaluate the system’s compliance with business requirements. This is the final level of testing, and checks whether the system is ready for release.

Unit tests in action

If app units are so small, why is testing them so important? Because each small piece of code depends on the other small pieces. Your web application is a bunch of small units working together; if you change one unit, something else can break as a consequence. Unit tests can automatically detect problematic parts of your code so you know where the problem is and what the correct behavior should be.

Different unit testing methods help to verify the behavior of small pieces of your application independently from other pieces of code. Let’s see how it works. Typically, a unit test consists of three phases:

  • Setting up the test for a particular piece of an application (called the system under test)
  • Performing the actual testing (interacting with the system under test)
  • Observing the resulting behavior and checking whether expectations were met

These three unit test phases are abbreviated as AAA, which stands for arrange, act, and assert. If the resulting behavior is in line with expectations, the unit test passes — everything works correctly. Otherwise it fails, indicating a problem with the system under test.

For example, let's see how we can apply a unit test to the following model.

app/models/organization.rb:
class Organization < ApplicationRecord
  resourcify :admin_roles, role_cname: 'AdminRole'
  validates :name, presence: true, uniqueness: true, length: { maximum: 80 }
  validates :description, length: { maximum: 500 }
  validates :service_email, email: true, presence: true, uniqueness: true
end

Below, we share code written with the PSpec framework that tests our model so that when we create a new organization it has a name, a description no longer than 500 characters, and a valid email address.

spec/models/organization_spec.rb:
require 'rails_helper'

RSpec.describe Organization, type: :model do
  subject do
    FactoryGirl.build(
      :organization,
      name: name,
      description: description,
      service_email: service_email
    )
  end
  let(:name) { FFaker::InternetSE.company_name_single_word }
  let(:description) { FFaker::Lorem.paragraph }
  let(:service_email) { FFaker::Internet.safe_email }

  describe '#validations' do
    context 'name validations:' do
      context 'invalid with nil name' do
        let(:name) { nil }
        it { is_expected.not_to be_valid }
      end

      context 'invalid with blank name' do
        let(:name) { '' }
        it { is_expected.not_to be_valid }
      end

      context 'invalid with name longer than 80 characters' do
        let(:name) { 'a' * 81 }
        it { is_expected.not_to be_valid }
      end
    end

    context 'service email validations:' do
      context 'invalid with blank email' do
        let(:service_email) { '' }
        it { is_expected.to be_invalid }
      end

      context 'invalid with non-email string' do
        let(:service_email) { 'somerandom email' }
        it { is_expected.not_to be_valid }
      end
    end

    context 'description validations:' do
      context 'invalid with description longer than 500 characters' do
        let(:description) { 'a' * 501 }
        it { is_expected.not_to be_valid }
      end
    end
  end
end

Benefits of unit testing

Now that we know how different types of testing work, let’s learn more about unit testing and how you can benefit from it. Many people believe that unit testing is a waste of time if they use automated tests. That’s not true.

UI automation testing is about high-level tests. Usually, the actions that automated UI tests check are complex. So lots of small steps are taken before reaching the final state. And any of these steps can fail. The result of an automated test depends on the quality of smaller components, and it can be quite challenging to find a problematic component.

On the other hand there are low-level unit tests. Their goal is to point out exactly which unit and which part of that unit doesn’t work correctly. But how exactly can we benefit from unit tests?

Find and fix bugs early

Unit tests are great for finding bugs before code is pushed to the quality assurance specialists. They help developers debug software at early stages. Finding and fixing bugs early with unit testing reduces the cost of debugging at later stages of development. 

Unit tests can become part of the continuous integration process. As software scales and the code base grows, unit tests run automatically. With unit tests, programmers can easily check the location of the failure and debug code.

Easier code changes and refactoring

Refactoring is the process of changing working code without changing the way the app behaves. Unit tests can relieve headaches as they ensure that code functions properly after these changes. Unit tests are important with code refactoring and when the code base becomes larger.

With unit tests, you get immediate feedback when you break code so you can refactor it safely. But as the software’s code base changes and grows, it’s important to also refactor unit tests to make sure you correctly test components.

Provide documentation

Many programmers find that unit tests may become working documentation of their code as unit tests are a working example of how to use a library or framework. Unit tests may be a useful form of documentation as they show how the original programmers intended their code to be used. Plus, unit tests are always up-to-date because they use the latest functionality and show all possible functionality.

Is this a benefit? It is, as documentation makes it easier for multiple developers to work together and hand over a project to another developer. But this is true only if developers know how to write unit tests clearly and write them regularly.

Automation frameworks for unit testing

Developers use unit testing frameworks to write unit tests quickly and easily since most programming languages don’t support unit testing with the built-in compiler. There are different frameworks to deal with unit testing. We’ll provide a few examples of tools we use at Yalantis to automate JavaScript and Ruby code testing.

Jasmine

Jasmine is a behavior-driven development framework for testing JavaScript code. It’s suited for websites, Node.js projects, or anywhere that JavaScript is used. Jasmine doesn’t rely on browsers and JavaScript frameworks. It also doesn’t require a DOM (document object model).

Jasmine offers easy initial setup with assertions, spies, and mocks. It provides all you need to develop unit tests as it comes with many basic features out-of-the-box. And it’s easy to implement in any development methodology. Jasmine supports asynchronous testing and uses spies for implementing test doubles.

The main benefit of Jasmine is that it’s browser, framework, and platform independent. In addition to being used for behavior-driven development (BDD), Jasmine can also be used for test-driven development (TDD), one of the main Agile development techniques. TDD is about writing your test code first so it can guide your implementation.

Jest

Jest was built by Facebook and based on Jasmine. It’s used by Instagram, Airbnb, Facebook, and many other companies. The Jest community appreciates its speed and simplicity. Jest runs tests simultaneously, so the whole test suite runs fast. It also provides the –watch feature to run only tests that are affected by your changes in the editor.

The main advantage of Jest is that it works out of the box with minimal setup and configuration. It comes with an assertion library and mocking support. Tests are written in behavior-driven development style like with most popular testing libraries.

The combo of Mocha and Chai

Mocha is a feature-rich JavaScript testing framework that runs on Node.js and a browser. At Yalantis, we use the Mocha testing framework with the Chai assertion library, which works well for JavaScript code used for data models, business logic, and utilities.

Mocha attracts lots of developers because of its simple and flexible modular nature. Mocha tests run serially, allowing accurate and flexible reporting while mapping uncaught exceptions to the correct test cases.

RSpec

RSpec is a BDD library for testing Ruby code. With RSpec, we can test an entire application’s behavior rather than only specific methods. When writing your code in RSpec, you create highly readable blocks of code that can be useful documentation if done right. The simplicity of the RSpec syntax makes it one of the most popular testing tools for Ruby applications. RSpec also has a large community with hundreds of contributors. It’s a domain-specific language (DSL) testing framework written in Ruby to test Ruby code and provides a readable approach to writing tests, offering flexibility in isolated parts of application testing.

Testing is the path to excellence. Unit tests help us write clean and elegant code. That may sound like just nice words. But unit testing actually is about better performance. With unit tests, lots of bugs can be found and fixed at early stages. Unit tests are also necessary when refactoring applications and implementing new features. Contact us if you have questions about unit testing for web applications.

Ten articles before and after

Real-Time Features: Best Use Cases and Reason To Implement Them In Your App

Golang and Node.js Comparison: Scalability, Performance, and Tools

How to Load Test an API to Ensure Your App Works Smoothly

Which Payment Gateway Integration to Choose for Your App

How To Choose a Technology Stack For Your Web App in 2021

How to Develop a Multi-Language Ruby on Rails Application and Take It Global

When and Why Angular Is a Good Technical Solution for Your Project

API Versioning: Which Gem to Pick for API Versioning on Ruby on Rails

What You Can Create with FFmpeg Filters and Frei0r Plugin Effects

How to Use the Android NDK When Writing an App in Kotlin