Ruby programming language illustration

How to test ruby gem: step by step tutorial with rspec example

In the previous part of this series we learned the basics of creating a Ruby gem. We created an initial directory structure, defined a gemspec, installed dependencies, and started to write the actual code.

Today, we are going to continue creating the Ruby gem and, specifically, we will take care of the testing suite essential for software internationalization. You will learn how to setup RSpec, create a Rails dummy application, define and test generator tasks, and how to test Rake tasks.

By integrating the localization process into the testing phase, you ensure that your gem not only works seamlessly in different environments but is also ready to support multiple languages from the start, boosting global accessibility. We will also integrate your code with Travis CI and Codecov services. Sound good? Then let’s get started!

 

    First part of the series: lokalise.com/blog/create-a-ruby-gem-basics

    Third part of the series: lokalise.com/blog/how-to-create-a-ruby-gem-publishing

    Setting up the testing suite

    Adding development dependencies

    So, first of all let’s add some more development dependencies into the gemspec file, like so:

    Gem::Specification.new do |spec|
      spec.add_development_dependency 'codecov', '~> 0.1'
      spec.add_development_dependency 'dotenv', '~> 2.5'
      spec.add_development_dependency 'rails', '~> 6.0.3'
      spec.add_development_dependency 'rake', '~> 13.0'
      spec.add_development_dependency 'rspec', '~> 3.6'
      spec.add_development_dependency 'rspec-rails', '~> 4.0'
      # rubocop dependencies...
      spec.add_development_dependency 'simplecov', '~> 0.16'
      spec.add_development_dependency 'vcr', '~> 6.0'
    end
    • Codecov is an integration for the Codecov.io service which displays test coverage results in a fancy way. We will return to it later.
    • Dotenv allows us to set environment variables (ENV) using the values inside a separate file. In our case these variables will contain the Lokalise API token and project ID.
    • We will employ Rails to run a dummy application.
    • Rake can be used to run certain tasks.
    • RSpec is our TDD framework. Of course, you can stick to Minitest, but I really love RSpec which seems more expressive to me.
    • Simplecov will allow us to measure code coverage for the testing suite.
    • VCR is a tool to record interactions with third-party services within special files (called cassettes) and replay them during subsequent test runs. This allows us to use pre-recorded fixture data instead of sending real requests every time.

    Don’t forget to run:

    bundle i

    before proceeding to the next section.

    Creating a Rails dummy application

    We will require a special “dummy” Rails app that will be used to test various features of our gem. This dummy will reside inside the spec/ folder. It should load only the bare minimum as we will not need features like ActionCable, ActiveJob, Spring, etc. Actually, we won’t even require ActiveRecord. Here are the commands to generate the app:

    mkdir spec
    cd spec
    rails new dummy --skip-spring --skip-listen --skip-bootsnap --skip-action-text --skip-active-storage --skip-action-cable --skip-action-mailer --skip-action-mailbox --skip-test --skip-system-test --skip-active-job --skip-active-record --skip-javascript

    Once the above command succeeds, open the spec/dummy/config/application.rb file and remove the following line:

    config.load_defaults 6.0

    We don’t need it because later I would like to run the testing suite using both Rails 5 and 6.

    Remove the Gemfile and Gemfile.lock from the spec/dummy folder because we will employ the Gemfile which resides in the project root. Open the spec/dummy/config/boot.rb file and replace its contents with the following:

    ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
    require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
    $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)

    This way we are pointing the Rails app toward the proper Gemfile.

    Let’s also add a tzinfo-data gem that should be loaded for Windows and JRuby users. Add the following to the Gemfile:

    # ...
    
    group :test do
      gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
    end

    Setting up RSpec

    Next, create a newspec/spec_helper.rb file with the following contents:

    require 'dotenv/load' # <============= 1
    require 'simplecov'
    
    SimpleCov.start 'rails' do # <============= 2
      add_filter 'spec/'
      add_filter '.github/'
      add_filter 'lib/generators/templates/'
      add_filter 'lib/lokalise_rails/version'
    end
    
    if ENV['CI'] == 'true' # <============= 3
      require 'codecov'
      SimpleCov.formatter = SimpleCov::Formatter::Codecov
    end
    
    Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f } # <============= 4
    
    ENV['RAILS_ENV'] = 'test' # <============= 5
    
    require_relative '../spec/dummy/config/environment' # <============= 6
    ENV['RAILS_ROOT'] ||= "#{File.dirname(__FILE__)}../../../spec/dummy" # <============= 7
    
    1. Load ENV variables using Dotenv. The contents for these variables will be hosted inside the .env file (we’ll create it a bit later).
    2. Start to measure code coverage for our testing suite. I’ve also provided some folders to ignore.
    3. If the testing suite runs on a continuous integration service (like Travis CI), the coverage data should then be sent to Codecov.io.
    4. Load support files from the spec/support directory. We’ll also create this directory later.
    5. We are loading the Rails dummy app using “test” environment.
    6. Load the environment.rb file to boot the dummy app.
    7. Set the Rails root.

    Now let’s also create a .rspec file inside the project root (outside of the spec/ folder). This file contains the RSpec options:

    --color
    --require spec_helper
    --order rand
    --format doc
    • Test results should be printed in color.
    • The spec_helper.rb file loads automatically.
    • The order of the tests is random.
    • The output formatter is doc.

    Setting ENV variables

    The next step is to provide values for the ENV variables. These variables, as you already know, are set by the Dotenv gem. So, create an .env file inside the project root:

    LOKALISE_API_TOKEN=123abc
    LOKALISE_PROJECT_ID=456.def

    Replace these sample values with the proper ones.

    Make sure to add this file to .gitignore. We have already done so in the previous part, but I’ll provide the .gitignore contents again just in case:

    *.gem
    coverage/*
    Gemfile.lock
    *~
    .bundle
    .rvmrc
    log/*
    measurement/*
    pkg/*
    .DS_Store
    .env
    spec/dummy/tmp/*
    spec/dummy/log/*.log
    

    Remember that the .env file must not be tracked by Git because otherwise your API token will be exposed!

    Finally, create the .env.sample file which will provide sample contents:

    LOKALISE_API_TOKEN=123abc
    LOKALISE_PROJECT_ID=456.def

    Developers will then copy-paste this file as .env on their local machines and replace the sample contents with the real values.

    Adding Rake tasks

    Let’s also add RSpec-related Rake tasks to the Rakefile. Here’s the new version of this file:

    require 'rake'
    
    begin
      require 'bundler/setup'
      Bundler::GemHelper.install_tasks
    rescue LoadError
      puts 'although not required, bundler is recommended for running the tests'
    end
    
    task default: :spec
    
    require 'rspec/core/rake_task'
    RSpec::Core::RakeTask.new(:spec)
    
    require 'rubocop/rake_task'
    RuboCop::RakeTask.new do |task|
      task.requires << 'rubocop-performance'
      task.requires << 'rubocop-rspec'
    end

    Configuration options: Tests

    Testing config method

    Okay, so at this point we are ready to write some tests! In the previous part we provided some config options for our gem (api_token, locales_path, and others). Therefore, let’s make sure that it is possible to adjust these options. Create a new spec/lib/lokalise_rails_spec.rb file:

    describe LokaliseRails do
      it 'is possible to provide config options' do
        described_class.config do |c|
          expect(c).to eq(described_class)
        end
      end
    end

    This test makes sure that when the config method is run, the LokaliseRails module is passed properly. This allows us to provide config in the following way:

    LokaliseRails.config do |c|
      c.api_token = '123'
    end

    Now change directory to the root of your project and run:

    rspec .

    This command will run all the tests inside the spec/ directory.

    Testing mandatory options

    Next, I would like to add a couple of tests for the mandatory options, namely the project_id and api_token. Specifically, we need to make sure that these options can be provided using the corresponding writers. However, I would like these tests to be isolated — in other words, they should not change the real config which will be loaded by the test dummy Rails app later. Therefore, let’s take advantage of a class double and create a fake class:

    describe LokaliseRails do
      # ...
    
      describe 'parameters' do
        let(:fake_class) { class_double('LokaliseRails') }
      end
    end

    Now provide the test case, like so:

    describe LokaliseRails do
      # ...
    
      describe 'parameters' do
        let(:fake_class) { class_double('LokaliseRails') }
    
        it 'is possible to set project_id' do
          expect(fake_class).to receive(:project_id=).with('123.abc')
          fake_class.project_id = '123.abc'
        end
      end
    end

    So, we are expecting the fake class to receive a writer method with the proper argument. Then we are actually calling this method. This test will pass if the project_id= writer method was actually provided for the LokaliseRails module.

    To avoid false positives, let’s start by making this test fail (the “red” phase, as some devs would say). Open the lib/lokalise_rails.rb file and remove the project_id accessor:

    module LokaliseRails
      class << self
        attr_accessor :api_token # remove project_id
        # ... other code...
      end
    end

    Now run rspec . and you’ll see the following output:

    Randomized with seed 7575
    
    LokaliseRails
      is possible to provide config options
      parameters
        is possible to set project_id (FAILED - 1)
    
    Failures:
    
      1) LokaliseRails parameters is possible to set project_id
         Failure/Error: expect(fake_class).to receive(:project_id=).with('123.abc')
           the LokaliseRails class does not implement the class method: project_id=
         # ./spec/lib/lokalise_rails_spec.rb:12:in `block (3 levels) in <top (required)>'
    
    Finished in 0.058 seconds (files took 6.06 seconds to load)
    2 examples, 1 failure
    
    Failed examples:
    
    rspec ./spec/lib/lokalise_rails_spec.rb:11 # LokaliseRails parameters is possible to set project_id

    Great! This means our test actually does its job. Now add the project_id to the list of accessors to make the test pass (“green” phase):

    module LokaliseRails
      class << self
        attr_accessor :api_token, :project_id
      end
    end

    Run rspec . again. Here’s the result:

    Randomized with seed 3205
    
    LokaliseRails
      is possible to provide config options
      parameters
        is possible to set project_id
    
    Finished in 0.055 seconds (files took 6.07 seconds to load)
    2 examples, 0 failures
    
    Randomized with seed 3205
    
    Coverage report generated for RSpec to f:/rails/lokalise/lokalise_rails/coverage. 12 / 18 LOC (66.67%) covered.

    Note that we also can see the test coverage percentage. To learn more about the test coverage, open the coverage/index.html file in your favorite browser. This provides detailed information for each monitored file line by line.

    Now, using the same approach we can add the test for the api_token= method:

    describe LokaliseRails do
      # ...
    
      describe 'parameters' do
        let(:fake_class) { class_double('LokaliseRails') }
    
        # ...
    
        it 'is possible to set api_token' do
          expect(fake_class).to receive(:api_token=).with('abc')
          fake_class.api_token = 'abc'
        end
      end
    end
    

    While we could also test the reader methods, it is not really necessary: they will be tested implicitly in other specs. Therefore, let’s take care of other methods.

    Testing other options

    Use the below approach to test other options:

    describe LokaliseRails do
      describe 'parameters' do    
        # ...
    
        it 'is possible to set file_ext_regexp' do
          expect(fake_class).to receive(:file_ext_regexp=).with(Regexp.new('.*'))
          fake_class.file_ext_regexp = Regexp.new('.*')
        end
    
        it 'is possible to set import_opts' do
          expect(fake_class).to receive(:import_opts=).with(duck_type(:each))
          fake_class.import_opts = {
            format: 'json',
            indentation: '8sp'
          }
        end
    
        it 'is possible to set export_opts' do
          expect(fake_class).to receive(:export_opts=).with(duck_type(:each))
          fake_class.export_opts = {
            convert_placeholders: true,
            detect_icu_plurals: true
          }
        end
    
        it 'is possible to set import_safe_mode' do
          expect(fake_class).to receive(:import_safe_mode=).with(true)
          fake_class.import_safe_mode = true
        end
    
        it 'is possible to override locales_path' do
          expect(fake_class).to receive(:locales_path=).with('/demo/path')
          fake_class.locales_path = '/demo/path'
        end
    
        it 'is possible to set skip_file_export' do
          cond = ->(f) { f.nil? }
          expect(fake_class).to receive(:skip_file_export=).with(cond)
          fake_class.skip_file_export = cond
        end
      end
    end

    Nice!

    Installation task (Rails generator)

    So, we have created basic unit tests for the gem options. To adjust these options, the user should create a config/lokalise_rails.rb with the following content:

    LokaliseRails.config do |c|
      c.api_token = '123'
      c.project_id = '345.abc'
      # ... other options
    end

    While we may instruct the user to create this file manually, it’s not very convenient. What I would like to do is introduce a command like rails g lokalise_rails:install that creates the configuration file containing the list of all supported options and usage instructions. To achieve this, we need to define a special generator to run the installation task.

    Creating the generator

    Our generator will live under the lib folder, therefore create the following path: lib/generators/lokalise_rails/install_generator.rb. This new file should contain the following code:

    require 'rails/generators'
    
    module LokaliseRails
      module Generators
        class InstallGenerator < Rails::Generators::Base
          source_root File.expand_path('../templates', __dir__)
    
          desc 'Creates a LokaliseRails config file.'
    
          def copy_config
            template 'lokalise_rails_config.rb', "#{Rails.root}/config/lokalise_rails.rb"
          end
        end
      end
    end

    This code will simply take the template lokalise_rails_config.rb from the ..templates/ directory and copy-paste it to the config/ folder of the Rails app. The target file will be named lokalise_rails.rb. You can find additional explanations for these methods in the official documentation.

    Next we’ll need a template to copy, thus create a new path: lib/generators/templates/lokalise_rails_config.rb. This is the file that will be copied in the user’s application:

    require 'lokalise_rails'
    
    LokaliseRails.config do |c|
      # These are mandatory options that you must set before running rake tasks:
      # c.api_token = ENV['LOKALISE_API_TOKEN']
      # c.project_id = ENV['LOKALISE_PROJECT_ID']
    
      # Provide a custom path to the directory with your translation files:
      # c.locales_path = "#{Rails.root}/config/locales"
    
      # Import options have the following defaults:
      # c.import_opts = {
      #   format: 'yaml',
      #   placeholder_format: :icu,
      #   yaml_include_root: true,
      #   original_filenames: true,
      #   directory_prefix: '',
      #   indentation: '2sp'
      # }
    
      # Safe mode for imports is disabled by default:
      # c.import_safe_mode = false
    
      # Additional export options (only filename, contents, and lang_iso params are provided by default)
      # c.export_opts = {}
    
      # Provide additional file exclusion criteria for exports (by default, any file with the proper extension will be exported)
      # c.skip_file_export = ->(file) { file.split[1].to_s.include?('fr') }
    
      # Regular expression to use when choosing the files to extract from the downloaded archive and upload to Lokalise
      # c.file_ext_regexp = /\.ya?ml\z/i
    end
    

    We provide explanations for each option, which makes the lives of our users a bit easier. Now let’s add some tests for our new installation task.

    Testing the installation task

    Create a new spec/lib/generators/lokalise_rails/install_generator_spec.rb file. Our test case will be very simple because we only need to make sure that running the given task results in creating the proper file. To run the task programmatically, you can call the start method:

    require 'generators/lokalise_rails/install_generator'
    
    describe LokaliseRails::Generators::InstallGenerator do
      it 'installs config file properly' do
        described_class.start
        expect(File.file?(config_file)).to be true
      end
    end
    

    config_file is a helper method that is going to return path to the lokalise_rails.rb configuration file. Such methods are usually defined in the support folder. Therefore, create a new  spec/support/file_manager.rb file:

    module FileManager
      def config_file
        "#{Rails.root}/config/lokalise_rails.rb"
      end
    end

    We also need to include this module in our tests, so adjust the spec/spec_helper.rb file in the following way:

    # ... other code ...
    
    RSpec.configure do |config|
      config.include FileManager
    end

    Now you can run the rspec . command and observe test results. The lokalise_rails.rb file should be created inside the config/ folder of your dummy Rails app.

    The problem, however, is that we are not doing any cleanup after running this test. In other words, the created config file is not being removed afterwards. Also, it might be a good idea to try and remove the config before running the test to make it isolated. This problem can be easily solved with before and after hooks:

    require 'generators/lokalise_rails/install_generator'
    
    describe LokaliseRails::Generators::InstallGenerator do
      before :all do
        remove_config
      end
    
      after :all do
        remove_config
      end
    
      # your test...
    end
    

    remove_config is also a helper method that we can define inside the support/file_manager.rb file:

    require 'fileutils'
    
    module FileManager
      def remove_config
        FileUtils.remove_file config_file if File.file?(config_file)
      end
    end
    

    Note that I’m utilizing the remove_file method provided by the FileUtils module. This method allows us to delete a file by providing full path to it. Don’t forget to import the FileUtils module before using it.

    And that’s it: our installation task is done!

    Rake tasks

    Creating and registering a Rails task with railtie

    The next step is to define and register two Rake tasks that the user will run to export or import translation files. These tasks will reside inside the lib/tasks/lokalise_rails_tasks.rake, for instance:

    require 'rake'
    require "#{Rails.root}/config/lokalise_rails" # <======= 1
    
    namespace :lokalise_rails do
      task :import do # <======= 2
        LokaliseRails::TaskDefinition::Importer.import!
      end
    
      task :export do # <======= 3
        LokaliseRails::TaskDefinition::Exporter.export!
      end
    end
    1. Before running the tasks, we are trying to load the config file. This file is created by the installation task we added a minute ago.
    2. To call this task, one should write rails lokalise_rails:import. The task delegates all work to the import! method which we are going to create later.
    3. Use the rails lokalise_rails:export command to run this task.

    We must register these tasks before using them. To register a task, you need to create a railtie — a block of code that extends Rails core functionality. We are going to place our railtie inside the lib/lokalise_rails/railtie.rb file:

    module LokaliseRails
      class Railtie < Rails::Railtie
        rake_tasks do
          load 'tasks/lokalise_rails_tasks.rake'
        end
      end
    end

    We also need to load this railtie inside the lib/lokalise_rails.rb file and make sure that the Rails framework is loaded:

    require 'lokalise_rails/railtie' if defined?(Rails)
    
    module LokaliseRails
      # ... options here ...
    end

    Rake task definitions

    So, we’ve created the tasks, but all the heavy lifting will be done by the import! and export! methods, therefore let’s draft them now. Start by creating a new lib/lokalise_rails/task_definition/base.rb file. This file will host a base class. The import and export task will inherit from it:

    module LokaliseRails
      module TaskDefinition
        class Base
        end
      end
    end
    

    Now create a lib/lokalise_rails/task_definition/importer.rb file:

    module LokaliseRails
      module TaskDefinition
        class Importer < Base
          class << self
            def import!
              $stdout.print 'Task complete!'
              true
            end
          end
        end
      end
    end
    

    We’ll take care of the actual implementation later. Note that I’m explicitly using the $stdout variable to print the success message. This is required for our testing purposes.

    Finally, create a lib/lokalise_rails/task_definition/exporter.rb file:

    module LokaliseRails
      module TaskDefinition
        class Exporter < Base
          class << self
            def export!
              $stdout.print 'Task complete!'
              true
            end
          end
        end
      end
    end
    

    It’s not much, but I’d like to create a minimal setup required to make the tests pass.

    Load these three files inside the lib/lokalise_rails.rb file:

    require 'lokalise_rails/task_definition/base'
    require 'lokalise_rails/task_definition/importer'
    require 'lokalise_rails/task_definition/exporter'
    require 'lokalise_rails/railtie' if defined?(Rails)
    
    module LokaliseRails
      # ... options ...
    end

    Now we can proceed to testing our Rake tasks.

    Testing Rake tasks

    First and foremost, we need to load the available Rake tasks in the spec/spec_helper.rb before running them programmatically. Add the following line to the bottom of the spec_helper.rb file:

    # ... other code
    
    Rails.application.load_tasks

    Create a new spec/lib/tasks/import_task_spec.rb file. What should we test for? Well, the import task should output the success message to STDOUT if is everything is okay. This can be tested using the following code snippet:

    expect(import_task_call_here).to output(/complete!/).to_stdout

    Thing is, this expectation requires a callable object (lambda or procedure). To run a Rake task programmatically, you can use the following approach:

    Rake::Task['lokalise_rails:import'].execute

    Which means that the expectation transforms to the this:

    expect(-> { Rake::Task['lokalise_rails:import'].execute }).to output(/complete!/).to_stdout

    This construct is pretty complex, so let’s create a new support file: spec/support/rake_utils.rb with the following content:

    module RakeUtils
      def import_executor
        -> { Rake::Task['lokalise_rails:import'].execute }
      end
    
      def export_executor
        -> { Rake::Task['lokalise_rails:export'].execute }
      end
    end
    

    These methods return callable objects to run import and export tasks. Include this module inside the spec/spec_helper.rb:

    # ... other code 
    
    RSpec.configure do |config|
      config.include FileManager
      config.include RakeUtils # <=======
    end
    
    Rails.application.load_tasks

    Now, create a new test case inside the import_task_spec.rb file:

    RSpec.describe LokaliseRails do
      it 'import rake task is callable' do
        expect(import_executor).to output(/complete!/).to_stdout
      end
    end

    Also, add another spec/lib/tasks/export_task_spec.rb file:

    RSpec.describe LokaliseRails do
      it 'runs export rake task properly' do
        expect(export_executor).to output(/complete!/).to_stdout
      end
    end

    The tests are ready but there’s one problem. Our Rake tasks try to load the config/lokalise_rails.rb file containing the config options. This means that this file has to be created before running all the tests. Let’s take care of this issue now.

    Creating a sample config file

    Open the spec/support/file_manager.rb file and add a new method:

    require 'fileutils'
    
    module FileManager
      def add_config
        data = <<~DATA
          require 'lokalise_rails'
          LokaliseRails.config do |c|
            c.api_token = ENV['LOKALISE_API_TOKEN']
            c.project_id = ENV['LOKALISE_PROJECT_ID']
          end
        DATA
    
        File.open(config_file, 'w+:UTF-8') do |f|
          f.write data
        end
      end
    
      # ... other methods... 
    end

    This method will create a new config file with the required options. I’m using heredoc to provide the file contents.

    Now tweak the spec/spec_helper.rb in the following way:

    # ... other code 
    
    include FileManager
    
    add_config
    
    Rails.application.load_tasks

    We are creating our configuration file before any test is run. Problem is, the test for the installation task removes the config inside the after hook. Therefore, let’s re-create the config by tweaking the spec/lib/generators/lokalise_rails/install_generator_spec.rb, for instance:

    require 'generators/lokalise_rails/install_generator'
    
    describe LokaliseRails::Generators::InstallGenerator do
      before :all do
        remove_config
      end
    
      after :all do
        remove_config
        add_config # <==========
      end
    
      # ...
    end

    Now you may run the rspec . command and observe the test results!

    Integrating with Travis CI and Codecov

    Before wrapping up this article, I also wanted to show how to integrate your plugin with Travis CI and Codecov services. Travis CI is a continuous integration service that specifically allows you to run your test suite using different versions of Ruby and Rails. It also enables us to deploy the code automatically to the target server. Codecov, in turn, monitors test coverage and displays results in a beautiful way.

    Setting up Travis CI

    To get started, create a new repository on GitHub (Bitbucket or GitLab). Next, open travis-ci.org and sign up using your GitHub account (it’s free for open source projects). Then click on your avatar in the top right corner and open ‘Settings’. Click ‘Sync account’ to load the available repositories. After the sync is done, find your new repository in the list and enable it by toggling the switch next to its name. Click ‘Settings’ next to your repository and find the ‘Environment variables’ section. Create LOKALISE_API_TOKEN and LOKALISE_PROJECT_ID variables with the proper values. The values won’t be shown in the logs so you’re safe.

    Now we need to create a new .travis.yml file in the root of your project:

    language: ruby
    
    rvm:
    - 2.5.8
    - 2.6.6
    - 2.7.1
    
    install: bundle install --retry=3
    
    before_install:
      - gem update bundler
    
    env:
      - 'TEST_RAILS_VERSION="~> 5.1.6"'
      - 'TEST_RAILS_VERSION="~> 5.2.3"'
      - 'TEST_RAILS_VERSION="~> 6.0.3"'

    We are instructing it to use Ruby language and run the testing suite with Ruby 2.5.8, 2.6.6, and 2.7.1. Also, I would like to use different versions of Rails for testing. To properly install these Rails versions, update the gemspec, like so:

    if ENV['TEST_RAILS_VERSION'].nil?
      spec.add_development_dependency 'rails', '~> 6.0.3'
    else
      spec.add_development_dependency 'rails', ENV['TEST_RAILS_VERSION'].to_s
    end

    So, on Travis CI we’ll use three different Rails versions to run the tests. Locally you’ll utilize Rails 6.0.x only.

    Setting up Codecov

    Navigate to codecov.io and sign up with your GitHub account (this service is free for open source projects). After signing up, click on your account name and search for your new repository. Basically, this is it: no additional configuration is needed as we’ve already added Codecov-related code to the spec_helper.rb file.

    At this point we are ready to get rolling. Commit your changes and push them to GitHub (make sure that the spec/dummy folder does not have its own .git directory!). Return to Travis CI and open the ‘Current’ tab for your repository. You will see something like this:

    So, the tests are being run for every Ruby and Rails version we’ve specified. To get more information on any job, click on the specific build. After all tests have passed, return to Codecov and update the page. You’ll see the code coverage results:

    You can find detailed information by clicking on the lib folder and proceeding to individual files. Codecov provides more insights (especially when you have added more commits), so make sure to browse other tabs.

    Badges

    We can also add fancy-looking badges to the README of our gem. These badges will display test coverage and the results of the most recent test run. To do that, add the following contents to the README.md:

    [![Build Status](https://travis-ci.org/USERNAME/REPO_NAME.svg?branch=master)](https://travis-ci.org/USERNAME/REPO_NAME)
    [![Test Coverage](https://codecov.io/gh/USERNAME/REPO_NAME/graph/badge.svg)](https://codecov.io/gh/USERNAME/REPO_NAME)

    Make sure to replace USERNAME with your name (in my case that’s bodrovis) and REPO_NAME with the name of your repository (lokalise_rails in my case). Commit the changes and push to GitHub:

    git add .
    git commit -am "added badges [skip ci]"
    git push

    Note the [skip ci] part of the commit message. It makes sure that Travis CI does not re-run all the tests again because we have not modified any codebase.

    Return to your GitHub repo and check out your cool new badges:

    Conclusion

    That’s it for today, guys. In this article we have seen how to create a Ruby gem and introduce a testing functionality. We have created a new generator, two Rake tasks, added tests for them, and integrated our repository with Travis CI and Codecov. Not bad, eh? In the next part of this series we will flesh out the gem and see how to test third-party APIs and work with ZIP files. See you really soon!

    Return to the first part

    Proceed to the third part

    Related articles
    Stop wasting time with manual localization tasks. 

    Launch global products days from now.