tests/testthat/test-capture-requests.R

d <- tempfile()
dl_file <- tempfile()
webp_file <- tempfile()

# The webfake URL will be something like 127.0.0.1:port, and port may vary
# so the mock paths will be different every time it runs
httpbin_mock_url <- build_mock_url(request(httpbin$url("")))

test_that("We can record a series of requests (a few ways)", {
  with_mock_path(d, {
    capture_requests({
      # <<- assign these so that they're available in the next test_that too
      r1 <<- request(httpbin$url("/get")) %>% req_perform()
      r2 <<- request(httpbin$url("")) %>% req_perform()
      r3 <<- request(httpbin$url("/status/418")) %>%
        req_error(is_error = ~FALSE) %>%
        req_perform()
      r4 <<- request(httpbin$url("/put")) %>%
        req_method("PUT") %>%
        req_perform()
    })
    # Now do some with start/stop
    start_capturing()
    r5 <<- request(httpbin$url("/response-headers")) %>%
      req_url_query(`Content-Type` = "application/json") %>%
      req_perform()
    # Now, some requests that write to disk
    r6 <<- request(httpbin$url("/anything")) %>%
      req_perform(path = dl_file)
    r7 <<- request(httpbin$url("/image/webp")) %>%
      req_perform(path = webp_file)
    r8 <<- request(httpbin$url("/status/202")) %>% req_perform()
    r9 <<- request(httpbin$url("/status/200")) %>% req_perform()
    stop_capturing()
  })

  # If these were recorded against httpbin.org, it would look like this:
  expected_files <- c(
    "httpbin.org.html", # it's HTML, and we now support that simplified
    "httpbin.org/anything.json",
    "httpbin.org/get.json",
    "httpbin.org/image/webp.R", # Not a simplifiable format, so .R
    "httpbin.org/image/webp.R-FILE", # The `write_disk` location
    "httpbin.org/put-PUT.json", # Not a GET, but returns 200
    "httpbin.org/response-headers-ac4928.json",
    "httpbin.org/status/200.txt", # empty 200 response "text/plain", so .txt
    "httpbin.org/status/202.R", # Not 200 response, so .R
    "httpbin.org/status/418.R" # Not 200 response, so .R
  )
  # But since we don't use httpbin anymore, they're in the localhost-port dir
  expected_files <- sub("httpbin.org", httpbin_mock_url, expected_files)
  expect_identical(sort(dir(d, recursive = TRUE)), expected_files)

  # Test the contents of the .R files
  teapot <- source(file.path(d, httpbin_mock_url, "status", "418.R"))$value
  expect_s3_class(teapot, "httr2_response")
  expect_identical(resp_status(teapot), 418L)
  expect_false("request" %in% names(teapot))
  # Make sure that our .html file has HTML
  expect_true(any(grepl(
    "</body>",
    suppressWarnings(readLines(file.path(d, paste0(httpbin_mock_url, ".html"))))
  )))
})

test_that("We can then load the mocks it stores", {
  # Look for mocks in our temp dir
  with_mock_path(d, {
    # Because the place we wrote out the file in our real request might not
    # naturally be in our mock directory, assume that that file doesn't exist
    # when we load our mocks.
    content_r6 <<- resp_body_json(r6)
    file.remove(dl_file)
    content_r7 <<- resp_body_raw(r7)
    file.remove(webp_file)

    mock_dl_file <- tempfile()
    mock_webp_file <- tempfile()
    with_mock_api({
      m1 <- request(httpbin$url("/get")) %>% req_perform()
      m2 <- request(httpbin$url("")) %>% req_perform()
      m3 <- request(httpbin$url("/status/418")) %>%
        req_error(is_error = ~FALSE) %>%
        req_perform()
      m4 <- request(httpbin$url("/put")) %>%
        req_method("PUT") %>%
        req_perform()
      m5 <- request(httpbin$url("/response-headers")) %>%
        req_url_query(`Content-Type` = "application/json") %>%
        req_perform()
      m6 <- request(httpbin$url("/anything")) %>%
        req_perform(path = mock_dl_file)
      m7 <- request(httpbin$url("/image/webp")) %>%
        req_perform(path = mock_webp_file)
      m8 <- request(httpbin$url("/status/202")) %>% req_perform()
      m9 <- request(httpbin$url("/status/200")) %>% req_perform()
    })
  })
  expect_identical(resp_body_json(m1), resp_body_json(r1))
  # Compare the HTML as text because the parsed HTML (XML) object has a
  # C pointer that is different between the two objects.
  expect_identical(
    enc2native(resp_body_string(m2)),
    enc2native(resp_body_string(r2))
  )

  expect_true(grepl("</body>", resp_body_string(m2)))
  expect_identical(resp_body_string(m3), resp_body_string(r3))
  expect_identical(resp_body_json(m4), resp_body_json(r4))
  expect_identical(resp_body_json(m5), resp_body_json(r5))
  expect_identical(resp_body_json(m6), content_r6)
  expect_identical(resp_body_raw(m7), content_r7)
  expect_equal(resp_status(m8), 202)
  expect_equal(resp_status(m9), 200)
  expect_equal(resp_content_type(m9), "text/plain")
  expect_false(resp_has_body(m9))
})

test_that("write_disk mocks can be reloaded even if the mock directory moves", {
  # This is an edge case caught because `crunch` package puts fixtures in
  # `inst/`, so you record to one place but when you read them from the
  # installed package, it's a different directory.
  d2 <- tempfile()
  dir.create(file.path(d2, httpbin_mock_url, "image"), recursive = TRUE)
  for (f in c("webp.R", "webp.R-FILE")) {
    file.rename(
      file.path(d, httpbin_mock_url, "image", f),
      file.path(d2, httpbin_mock_url, "image", f)
    )
  }
  with_mock_path(d2, {
    with_mock_api({
      m7b <- request(httpbin$url("/image/webp")) %>%
        req_perform(tempfile())
    })
  })
  expect_identical(resp_body_raw(m7b), content_r7)
})

with_mock_api({
  d2 <- tempfile()
  test_that("Recording requests even with the mock API", {
    capture_while_mocking(path = d2, {
      request("http://example.com/get/") %>% req_perform()
      request("https://test.api/object1/") %>% req_perform()
      request("http://httpbin.not/status/204/") %>% req_perform()
    })
    expect_setequal(
      dir(d2, recursive = TRUE),
      c("example.com/get.json", "test.api/object1.json", "httpbin.not/status/204.204")
    )
    expect_identical(
      readLines(file.path(d2, "example.com/get.json")),
      readLines("example.com/get.json")
    )
  })

  test_that("Loading 204 response status recorded with simplify=TRUE", {
    original <- request("http://httpbin.not/status/204/") %>% req_perform()
    expect_length(original$body, 0)
    expect_length(
      readLines(file.path(d2, "httpbin.not/status/204.204")),
      0
    )
    with_mock_path(d2,
      {
        mocked <- request("http://httpbin.not/status/204/") %>% req_perform()
        expect_length(mocked$body, 0)
      },
      replace = TRUE
    )
  })

  d3 <- tempfile()
  test_that("Using simplify=FALSE (and setting .mockPaths)", {
    with_mock_path(d3, {
      capture_while_mocking(simplify = FALSE, {
        request("http://example.com/get/") %>% req_perform()
        request("https://test.api/object1/") %>% req_perform()
        request("http://httpbin.not/status/204/") %>% req_perform()
      })
    })
    expect_setequal(
      dir(d3, recursive = TRUE),
      c("example.com/get.R", "test.api/object1.R", "httpbin.not/status/204.R")
    )
    response <- source(file.path(d3, "example.com/get.R"))$value
    expect_s3_class(response, "httr2_response")
    expect_identical(
      resp_body_json(response),
      resp_body_json(request("http://example.com/get/") %>% req_perform())
    )
  })

  test_that("Recorded JSON is prettified", {
    expect_length(
      readLines(file.path(d2, "example.com/get.json")),
      3L
    )
    skip("TODO: prettify when simplify=FALSE")
    response <- readLines(file.path(d3, "api/object1.R"))
  })

  test_that("Using options(httptest2.verbose = TRUE) works", {
    d4 <- tempfile()
    old <- options(httptest2.verbose = TRUE)
    on.exit(options(old))
    with_mock_path(d4, {
      capture_while_mocking(
        expect_message(
          expect_message(
            request("http://example.com/get/") %>% req_perform(),
            "Writing .*example.com.get.json"
          ),
          "Reading .*example.com.get.json"
        )
      )
    })
    expect_setequal(
      dir(d4, recursive = TRUE),
      c("example.com/get.json")
    )
    expect_identical(
      readLines(file.path(d4, "example.com/get.json")),
      readLines("example.com/get.json")
    )
  })
})

test_that("If the httr2 request function exits with an error, capture_requests warns", {
  capture_requests({
    with_mock(
      function(req) stop("Error!"),
      expect_warning(
        expect_error(
          request("http://httpbin.not/get") %>% req_perform()
        ),
        "Request errored; no captured response file saved"
      )
    )
  })
})

Try the httptest2 package in your browser

Any scripts or data that you put into this service are public.

httptest2 documentation built on May 29, 2024, 8:42 a.m.