Skip to contents

Where are my mocks recorded?

By default, the destination path for capture_requests() is relative to the current working directory of the process. This matches the behavior of with_mock_api(), which looks for files relative to its directory, which typically is tests/testthat/.

If you’re running capture_requests within a test suite in an installed package, or if you’re running interactively from a different directory, the working directory may not be the same as your code repository. If you aren’t sure where the files are going, set options(httptest.verbose=TRUE), and it will message the absolute path of the files as it writes them.

To change where files are being written or read from, use .mockPaths() (like base::.libPaths()) to specify a different directory.

How do I fix “non-portable file paths”?

If you see this error in R CMD build or R CMD check, it means that there are file paths are longer than 100 characters, which can sometimes happen when you record requests. httptest preserves the URL structure of mocks in file paths to improve the readability and maintainability of your tests, as well as to make visible the properties of your API. Indeed, the file-system tree view of the mock files gives a visual representation of your API. This value comes with a tradeoff: sometimes URLs can be long, and R doesn’t like that.

Depending on how long your URLs are, there are a few ways to save on characters without compromising readability of your code and tests.

A big way to cut long file paths is by using a request preprocessor: a function that alters the content of your ‘httr’ request before mapping it to a mock file. For example, if all of your API endpoints sit beneath https://language.googleapis.com/v1/, you could set a request preprocessor like:

set_requester(function (request) {
  gsub_request(request, "https\\://language.googleapis.com/v1/", "api/")
})

and then all mocked requests would look for a path starting with “api/” rather than “language.googleapis.com/v1/”, saving you (in this case) 23 characters.

You can also provide this function in inst/httptest/request.R, and any time your package is loaded (as when you run tests or build vignettes), this function will be called automatically. See vignette("redacting") for more.

You may also be able to economize on other parts of the file paths. If you’ve recorded requests and your file paths contain long ids like “1495480537a3c1bf58486b7e544ce83d”, depending on how you access the API in your code, you may be able to simply replace that id with something shorter, like “1”. The mocks are just files, disconnected from a real server and API, so you can rename them and munge them as needed.

Finally, if you have your tests inside a tests/testthat/ directory, and your fixture files inside that, you can save 9 characters by moving the fixtures up to tests/ and setting .mockPaths("../").

How do I switch between mocking and real requests?

httptest does not intend that every request in your test suite is something that could be run against a live server. There are practical reasons why you should be able to see, modify, and maintain test fixtures, rather than re-record them every time you make a change. Among the considerations:

  • In many cases, API responses contain way more content than is necessary to test your R code around them: 100 records when 2 will suffice, request metadata that you don’t care about and can’t meaningfully assert things about, and so on. In the interest of minimally reproducible examples, and of making tests readable, it often makes sense to take an actual API response and delete a lot of its content, or even to fabricate one entirely.
  • It’s good to keep an API mock fixed so you know exactly what is in it. If you re-recorded a Twitter API response of, say, the most recent 10 tweets with #rstats, the specific content will change every time you record it, so your tests can’t say much about what is in the response without having to rewrite them every time too.
  • Some conditions (rate limiting, server errors, e.g.) are difficult to test with real responses, but if you can hand-create a API mock with, say, a 503 response status code and test how your code handles it, you can have confidence of how your package will respond when that rare event happens with the real API.
  • Re-recording all responses can make for a huge code diff, which can blow up your repository size and make code review harder.

That said, it can be worthwhile to have a subset of tests that can be run against a live API so that you can detect and respond to API changes. One option is to set up some tests with the with_mock_dir() context instead of with_mock_api(). For example:

with_mock_dir("httpbin-get", {
  a <- GET("https://httpbin.org/get")
  print(a)
})

The first time you run the code, it will create the directory tests/testthat/httpbin-get, and create mock files under it. The next times you run it, it will use the mock files in tests/testthat/httpbin-get. To re-record, simply delete the directory.

Another option is to have a secondary integration test suite in your code repository, a directory outside of the standard R package directories and included in .Rbuildignore so that it doesn’t get packaged. You could run this locally with testthat::test_dir(), and you could run it on continuous integration builds by replacing the tests/testthat directory with the alternate test directory.