R - mocking API requests with `gh` package
Asked Answered
D

2

6

I am trying to mock the output of a gh API request:

httptest2::with_mock_dir("gh", {
  test_that("api works", {
    gh::gh("GET /repos/r-lib/gh")
  })
})

I am trying to set up testing for custom functions that routinely make API calls to GitHub and I am using gh to make these requests. I am following this tutorial as guidance: https://books.ropensci.org/http-testing/

However, no directory is created when this function is run. Is there anyway to capture the output of gh::gh and store it as a mock API return so that I can run my tests without needing GitHub authentication or even an internet connection?

Devoice answered 3/7, 2022 at 23:48 Comment(0)
S
4

httptest2 is specifically designed to test httr2 requests:

This package helps with writing tests for packages that use httr2

Unfortunately, gh uses httr:

Imports:
    cli (>= 3.0.1),
    gitcreds,
    httr (>= 1.2),
    ini,
    jsonlite

This means that you can't directly use httptest2 with gh.

However, using gh source code, you can extract the parameters of the GET request sent to httr by gh:

gh_get <- function(endpoint, ..., per_page = NULL, .token = NULL, .destfile = NULL,
               .overwrite = FALSE, .api_url = NULL, .method = "GET",
               .limit = NULL, .accept = "application/vnd.github.v3+json",
               .send_headers = NULL, .progress = TRUE, .params = list()) {
  params <- c(list(...), .params)
  params <- gh:::drop_named_nulls(params)
  
  if (is.null(per_page)) {
    if (!is.null(.limit)) {
      per_page <- max(min(.limit, 100), 1)
    }
  }
  
  if (!is.null(per_page)) {
    params <- c(params, list(per_page = per_page))
  }
  
  req <- gh:::gh_build_request(
    endpoint = endpoint, params = params,
    token = .token, destfile = .destfile,
    overwrite = .overwrite, accept = .accept,
    send_headers = .send_headers,
    api_url = .api_url, method = .method
  )
  req
}

req <- gh_get("GET /repos/r-lib/gh")
req

#$method
#[1] "GET"

#$url
#[1] "https://api.github.com/repos/r-lib/gh"

#$headers
#                      User-Agent                           Accept 
#   "https://github.com/r-lib/gh" "application/vnd.github.v3+json" 

#$query
#NULL

#$body
#NULL

#$dest
#<request>
#Output: write_memory

This allows with the example you provided to use httr2 to send the same request :

library(httr2)
resp_httr2 <- request(base_url=req$url) %>%
              req_perform() %>%
              resp_body_json()


If you are mainly interested in json content, the results are the same, only the attributes differ :

resp_gh <- gh::gh("GET /repos/r-lib/gh")

all.equal(resp_gh,resp_httr2,check.attributes=FALSE)
#[1] TRUE

If you want to use httptest2, switching to httr2 would work:

with_mock_dir("gh", {
   test_that("api works", {
   resp <- request(base_url=req$url) %>%
     req_perform() %>%
     resp_body_json()
     expect_equal(resp$full_name,"r-lib/gh")})
 })
#Test passed 🎉
#[1] TRUE

Offline testing now works because gh\api.github.com directory was created by httptest2.

Sepulchre answered 8/7, 2022 at 16:7 Comment(0)
P
0

Maybe you can take inspiration from tests/testthat/test-mock-repos.R

res <- gh(
    TMPL("/repos/{owner}/{repo}"),
    owner = "gh-testing",
    repo = test_repo,
    .token = tt()
  )
  expect_equal(res$name, test_repo)
  expect_equal(res$description, "Test repo for gh")
  expect_equal(res$homepage, "https://github.com/r-lib/gh")
  expect_false(res$private)
  expect_false(res$has_issues)
  expect_false(res$has_wiki)

A GET method would not create any directory.

Papery answered 7/7, 2022 at 4:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.