[RSpec] Use WebMock to test logic using an external API

In the article [Ruby] Procedure for creating an external API linkage class using Faraday, we introduced how to link with an external API using Faraday.

In logic test code that uses an external API, it is common to stub HTTP requests instead of using the actual API. In Rails, you can stub HTTP requests by using a gem called webmock.

This time, I will introduce how to use WebMock to create logic test code linked with an external API.

The target class of the test code

This time, we will create a test code for a class called QiitaClientAPI that works with the Qiita API. The class implements a method called get_items to get a list of articles from Qiita.

The specific source code is as follows.

lib/qiita_api_client.rb


class QiitaApiClient
  class HTTPError < StandardError
    def initialize(message)
      super "connection failed: #{message}"
    end
  end

  class << self
    def connection
      Faraday::Connection.new('https://qiita.com') do |builder|
        builder.authorization :Bearer, "#{Rails.application.credentials.qiita[:token]}"
        builder.request  :url_encoded #URL-encode request parameters
        builder.response :logger #Output the response to standard output
        builder.adapter Faraday.default_adapter #Adapter selection. The default is Net::HTTP
        builder.response :json, :content_type => "application/json" #JSON parse the response body
      end
    end

    def get_items
      begin
        response = connection.get(
          '/api/v2/items'
        )
        response.body
      rescue Faraday::ConnectionFailed => e
        raise QiitaApiClient::HTTPError.new(e.message)
      end
    end
  end
end

Preparation

Prepare to run the test code.

RSpec setup

This time we will use RSpec as the testing framework. Add rspec-rails to your Gemfile.

Gemfile


group :development, :test do
  gem 'rspec-rails'
end

Install gem and set up RSpec.

$ bundle install
$ rails generate rspec:install

Webmock installation

Add webmock to your Gemfile and install it.

Gemfile


gem 'webmock'
$ bundle install

Test code implementation

Below, we will introduce how to write test code using WebMock.

Normal system test

The normal test code is as follows.

spec/lib/qiita_api_client_spec.rb


require 'rails_helper'

describe 'QiitaApiClient' do
  before do
    WebMock.enable! #Enable WebMock
    allow(Rails.application.credentials).to receive(:qiita).and_return({token: '123'})
  end
  describe '.get_items' do
    let(:response_body) { [{ "title": "test" }].to_json }
    before do
      WebMock.stub_request(:get, "https://qiita.com/api/v2/items").
        with(
          headers: {'Authorization' => "Bearer 123"}
        ).
        to_return(
          body: response_body,
          status: 200,
          headers: { 'Content-Type' => 'application/json' }
        )
    end
    it 'Data can be obtained' do
      expect(QiitaApiClient.get_items).to eq(JSON.parse(response_body))
    end
  end
end

I have enabled WebMock with WebMock.enable! and created an HTTP request stub at WebMock.stub_request.

By using WebMock, you can describe the behavior of "what kind of request is returned and what kind of response is returned". This allows you to create a simulated situation where data is exchanged via an external API.

In addition, the part of allow (Rails.application.credentials) described in before sets a dummy value for Rails.application.credentials.qiita [: token] described in the application code. Is for.

Abnormal system (exception occurrence) test

If the application code cannot communicate normally with the external API, it raises the exception QiitaApiClient :: HTTPError with an error message.

The test code of the abnormal system that confirms that the exception has occurred is as follows.

spec/lib/qiita_api_client_spec.rb


require 'rails_helper'

describe 'QiitaApiClient' do

  before do
    WebMock.enable!
    allow(Rails.application.credentials).to receive(:qiita).and_return({token: '123'})
  end

  describe '.get_items' do
    let(:response_body) { { "message": "error_message", "type": "error_type" }.to_json }
    before do
      WebMock.stub_request(:get, "https://qiita.com/api/v2/items").
        to_raise(Faraday::ConnectionFailed.new("some error"))
    end
    it 'Exception is raised' do
      #Exception test is expect()Not expect{}So be careful
      expect{QiitaApiClient.get_items}.to raise_error(QiitaApiClient::HTTPError, "connection failed: some error")
    end
  end
end

I have created a stub that raises an exception with to_raise in WebMock and confirmed that the exception is raised with raise_error in expect.

Final test code

The final test code that summarizes the normal system and abnormal system introduced this time is as follows.

spec/lib/qiita_api_client_spec.rb


require 'rails_helper'

describe 'QiitaApiClient' do
  let(:qiita_base_uri) { 'https://qiita.com/api/v2' }
  before do
    WebMock.enable!
    allow(Rails.application.credentials).to receive(:qiita).and_return({token: '123'})
  end

  describe '.get_items' do
    subject { QiitaApiClient.get_items }
    context 'success' do
      let(:response_body) { [{ "title": "test" }].to_json }
      before do
        WebMock.stub_request(:get, "#{qiita_base_uri}/items").
          with(
            headers: {'Authorization' => "Bearer 123"}
          ).
          to_return(
            body: response_body,
            status: 200,
            headers: { 'Content-Type' => 'application/json' }
          )
      end
      it 'Data can be obtained' do
        expect(subject).to eq(JSON.parse(response_body))
      end
    end
    context 'Failure' do
      before do
        WebMock.stub_request(:get, "#{qiita_base_uri}/items").
          to_raise(Faraday::ConnectionFailed.new("some error"))
      end
      it 'Exception is raised' do
        expect{subject}.to raise_error(QiitaApiClient::HTTPError, "connection failed: some error")
      end
    end
  end
end

Summary

We will introduce how to use WebMock to create logic test code linked with an external API.

The flow of the test code using WebMock is as follows.

  1. Enable WebMock with Webmock.enable!
  2. Create an HTTP request stub with WebMock.stub_request
  3. Execute the method that uses the external API and verify the execution result.

I'm on Twitter (@ nishina555). I hope you will follow me!

Recommended Posts

[RSpec] Use WebMock to test logic using an external API
How to write an RSpec controller test
How to use "sign_in" in integration test (RSpec)
Introduction to RSpec 1. Test, RSpec
Use webmock with Rspec
How to use Chain API
Display weather forecast using OpenWeatherMap, an external API in Ruby
[Rails] Test code using Rspec
Test the behavior of the log output logger when using an external logging API such as SLF4J
[Ruby] Use an external API that returns JSON in HTTP request
Image upload using CarrierWave ~ Rspec test ~
How to insert an external library
[RSpec] How to write test code
[RSpec] Unit test (using gem: factory_bot)
[For Rails beginners] Summary of how to use RSpec (get an overview)
Java Artery-Easy to use unit test library
[Docker] Building an environment to use Hugo
How to play MIDI files using the Java Sound API (specify the MIDI device to use)