[RSpec] WebMock handles regular expressions and Array query strings

Introduction

WebMock is a well-known mock library that has been around for a long time, but I will summarize what I tried variously when trying to do a little detailed verification with RSpec as my own memo. We mainly handle verification methods for HTTP requests, assuming requests to external APIs.

environment

Ruby : 2.7.1 RSpec : 3.9.0 webmock : 3.8.3

Code to be tested

As a sample, this time, I will request to https://jsonplaceholder.typicode.com/.

sample.ruby


require 'net/http'

class Sample
  def request(params = {})
    URI.parse('https://jsonplaceholder.typicode.com/todos')
      .tap { |uri| uri.query = URI.encode_www_form(params) }
      .then { |uri| Net::HTTP.get_response(uri) }
      .then { |res| res.body if res.is_a?(Net::HTTPSuccess) }
  end
end

Uninflected word

Use webmock's stub_request to make a request to the specified URL a mock. In addition, the verification at the time of ʻexpect is performed by the pattern using ʻa_request.

sample_spec.rb


context 'Uninflected word' do
  before do
    stub_request(:get, 'https://jsonplaceholder.typicode.com/todos').and_return(status: 200, body: 'hoge')
  end

  it 'To be requested correctly' do
    expect(Sample.new.request).to eq 'hoge'
    expect(a_request(:get, 'https://jsonplaceholder.typicode.com/todos')).to have_been_made.once
  end
end

context 'Uninflected word 2 Verify query string' do
  before do
    stub_request(:get, 'https://jsonplaceholder.typicode.com/todos?userId=2').and_return(status: 200, body: 'hoge')
  end

  it 'To be requested correctly' do
    expect(Sample.new.request(userId: 2)).to eq 'hoge'
    expect(
      a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: { userId: 2 })
    ).to have_been_made.once
  end
end

--have_bee_made is a matcher for RSpec provided by WebMock. There are several other matchers available.

I want to use a wildcard-like regex

For example, you may want to change the URL you request from todos to todos / 1, or you may want to test by specifying more query strings, in which case you may want to match each request If you do not change the URL string of stub_request, it will not be mocked. That's a hassle, so use regular expressions to handle it.

stub_request(:get, /https:\/\/jsonplaceholder.typicode.com/).and_return(status: 200, body: 'hoge')

By doing this, all requests under the https://jsonplaceholder.typicode.com/ domain will be mocked and can be verified with ʻa_request`.

Regular expressions can also be used in ʻa_request`.

expect(a_request(:get, /https:\/\/jsonplaceholder.typicode.com/)).to have_been_made

However, you cannot validate the query string using with in ʻa_request` as shown below.

#This way of writing is NG
expect(a_request(:get, /https:\/\/jsonplaceholder.typicode.com/).with(query: { userId: 2)).to have_been_made

How to validate Array query strings

This section describes the method when you want to verify the query string that specifies multiple identical keys as shown below.

Rails default

For Rails applications, the default is to add [] as shown below. ([] is encoded so it's exactly % 5B% 5D)

?userId[]=1&userId[]=2&userId[]=3

It is OK if you specify the array in the hash of query in with as shown below. You can write like rails.

expect(
  a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: { userID: [1, 2, 3] })
).to have_been_made

Rails If not default

For non-Rails applications such as external APIs, most patterns do not include [] as shown below.

?userId=1&userId=2&userId=3

In such a case, set the symbol : flat_array in WebMock as shown below.

WebMock::Config.instance.query_values_notation = :flat_array

The verification method is to specify the value as a string with the query hash of with as shown below.

expect(
  a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: 'userID=1&userID=2&userID=3')
).to have_been_made

If you don't like to write the string directly, you can convert it to a string using ʻURI.encode_www_form` etc.

expect(
  a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: URI.encode_www_form(userId: [1, 2, 3]))
).to have_been_made

If you set flat_array, the query string is sorted by with

If you set WebMock :: Config.instance.query_values_notation =: flat_array, but when validating the query string with with, you need to pass the values sorted by character order of the keys.

The query string is requested as ʻuserId = 1 & aa = 1 when the code under test in the title is executed, but the sorted string ʻaa = 1 & userId = 1 is passed to query of with. It was useless without it.

WebMock::Config.instance.query_values_notation = :flat_array

Sample.new.request(userId: 1, aa: 1)

# OK
expect(
  a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: 'aa=1&userId=1')
).to have_been_made

#hash is OK
expect(
  a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: { userId: 1, aa: 1 })
).to have_been_made

#This is NG
expect(
  a_request(:get, 'https://jsonplaceholder.typicode.com/todos').with(query: 'userId=1&aa=1')
).to have_been_made

reference

https://github.com/bblimke/webmock

Recommended Posts

[RSpec] WebMock handles regular expressions and Array query strings
colorize and regular expressions
[Java] Comparison method of character strings and comparison method using regular expressions
Regular expressions
Switch beans with @ConditionalOnExpression and SpEL regular expressions
Distinguish between integers and decimals with regular expressions