<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>R-Pkg-Dev on TanHo.ca</title><link>https://tanho.ca/tags/r-pkg-dev/</link><description>Recent content in R-Pkg-Dev on TanHo.ca</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright> © 2026 Tan Ho. Unless otherwise specified, content is licensed with [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).</copyright><lastBuildDate>Sun, 15 Dec 2024 18:36:25 -0500</lastBuildDate><atom:link href="https://tanho.ca/tags/r-pkg-dev/index.xml" rel="self" type="application/rss+xml"/><item><title>Adventures in HTTP Testing for R</title><link>https://tanho.ca/posts/2021-03-08_httptesting/</link><pubDate>Mon, 08 Mar 2021 00:00:00 +0000</pubDate><guid>https://tanho.ca/posts/2021-03-08_httptesting/</guid><description>&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Embrace unit testing karma&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;Karma says:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Do good things and good things will happen to you.&lt;/li&gt;&#10;&lt;li&gt;Do them the way you know.&lt;/li&gt;&#10;&lt;li&gt;Do them the way you like.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Karma is flexible. Testing needs flexibility.&lt;/p&gt;&#10;&lt;p&gt;Karma thrives on creativity. Testing needs creativity. - &lt;a href="https://www.artima.com/weblogs/viewpost.jsp?thread=203994" target="_blank" rel="noreferrer"&gt;Testivus&lt;/a&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&lt;p&gt;While developing &lt;a href="https://ffscrapr.dynastyprocess.com" target="_blank" rel="noreferrer"&gt;ffscrapr&lt;/a&gt;, I&amp;rsquo;ve taken a&#10;deeper dive into HTTP testing for R packages. I&amp;rsquo;ve settled on using httptest and&#10;find it very convenient path to building and using mock data, especially since I&amp;rsquo;m&#10;using &lt;a href="https://httr.r-lib.org/" target="_blank" rel="noreferrer"&gt;httr&lt;/a&gt; extensively within the package. I wish&#10;Scott Chamberlain &amp;amp; Maelle Salmon&amp;rsquo;s new book &lt;a href="https://books.ropensci.org/http-testing/" target="_blank" rel="noreferrer"&gt;HTTP testing in R&lt;/a&gt;&#10;had been written before I started - that would have been incredibly helpful as a&#10;guiderail for my experimentations! Here are some thoughts on what I&amp;rsquo;ve discovered&#10;while testing &lt;code&gt;ffscrapr&lt;/code&gt;, mostly trying to capture parts of my experiences that&#10;differ from what&amp;rsquo;s already in the HTTP testing book.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Why test?&#10; &lt;div id="why-test" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-test" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Testing is about making sure that the package (and functions) work as intended,&#10;&lt;strong&gt;in spite of&lt;/strong&gt; external factors. With API packages, sources of errors can include:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;internet connectivity (both from the user and from the API)&lt;/li&gt;&#10;&lt;li&gt;API response changes (format changes, variable naming, etc)&lt;/li&gt;&#10;&lt;li&gt;dependency changes (this could include base R versions, operating systems,&#10;as well as the more obvious package dependencies)&lt;/li&gt;&#10;&lt;li&gt;users doing unexpected things with your package (finding corner cases)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;A good test setup should test at least the first three of these (user-side corner&#10;cases being difficult to foresee at time of function definition), and ideally should&#10;test each area individually while holding the other two parts constant. For example,&#10;you want to test how the package handles internet connectivity &lt;strong&gt;without&lt;/strong&gt; worrying&#10;about whether the API response changes, or whether the dependencies have changed.&#10;You also want to test the package every time the dependencies change, without&#10;worrying about internet connectivity or API responses changing. And lastly, you&#10;want to test and make sure that the real API response has not changed, because&#10;it might return unexpected results. In addition to these concerns, another really&#10;important element to consider is making sure that it is easy to maintain the testing&#10;setup.&lt;/p&gt;&#10;&lt;p&gt;For &lt;code&gt;ffscrapr&lt;/code&gt;, I think the most important of these are testing the code for&#10;dependency changes and testing the code for API response changes. Internet&#10;connectivity seems fairly straightforward to write code for, and is worth doing&#10;at least once, but especially in a heavier-weight package like ffscrapr (which&#10;imports quite liberally from tidyverse packages like dplyr, purrr, tidyr etc),&#10;dependency testing is crucial to making sure the package continues to clean and&#10;process the data in the intended fashion. Also, fantasy platform APIs do change&#10;on a pretty frequent basis, adding and removing features nearly every year. It&amp;rsquo;s&#10;worth testing against the live APIs to make sure that &lt;code&gt;ffscrapr&lt;/code&gt; continues to&#10;operate as intended and without errors.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Why not skip CRAN testing?&#10; &lt;div id="why-not-skip-cran-testing" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-not-skip-cran-testing" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Many API packages skip all testing on CRAN - it&amp;rsquo;s a hassle to maintain, and the&#10;risk of being booted from CRAN can outweigh the effort of making sure tests don&amp;rsquo;t&#10;fail. I&amp;rsquo;m here to suggest otherwise! I think developers are missing out on one of&#10;CRAN&amp;rsquo;s most important benefits, which is being a massive dependency testing suite.&#10;Packages are tested on many different combinations of operating systems and R&#10;versions on a regular basis, and CRAN will also require your dependency packages&#10;to make sure that changes they make don&amp;rsquo;t break your package (and/or if they do,&#10;to give you advanced notice that it will break your package). This gives you time&#10;to make the appropriate changes to the package to accommodate the change!&lt;/p&gt;&#10;&lt;p&gt;Skipping these tests on CRAN is doing yourself a disservice, in my opinion,&#10;especially with the development of packages like &lt;a href="https://enpiar.com/r/httptest/" target="_blank" rel="noreferrer"&gt;httptest&lt;/a&gt;&#10;(which I use) and alternatives like vcr/webmockr - I think the extra work in making&#10;the package CRAN-testable (at least, to make sure that your R code processes the&#10;responses properly) is worth doing. These packages essentially create local copies&#10;of the data that the API would return from your http query, and redirect your&#10;request to read from the local files instead of letting your request go through&#10;to the API - allowing you to test while holding the API responses constant.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Storing mock data in a separate repository&#10; &lt;div id="storing-mock-data-in-a-separate-repository" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#storing-mock-data-in-a-separate-repository" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;In an ideal world, tests and files are small and can be stored alongside the test&#10;data, such that you don&amp;rsquo;t overrun CRAN&amp;rsquo;s limitations on compressed or installed&#10;package sizes (5MB, generally speaking). With &lt;code&gt;ffscrapr&lt;/code&gt;, I was finding that&#10;responses from certain sites (cough, Fleaflicker, ESPN, cough) were enormous -&#10;they package a whole whack of player data within each call, such that their&#10;responses were coming back at 5-10MB each!&lt;/p&gt;&#10;&lt;p&gt;In consulting the available documentation, I came across &lt;a href="https://github.com/nealrichardson/httptest/issues/11/" target="_blank" rel="noreferrer"&gt;this GitHub issue&lt;/a&gt; in the httptest&#10;repository, in which Neal Richardson describes some of his suggested approaches&#10;to solving this problem:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Creating a function to redact the responses on capture&lt;/li&gt;&#10;&lt;li&gt;Manually pruning the captured responses&lt;/li&gt;&#10;&lt;li&gt;Refactor larger functions so that they can be tested on smaller chunks of data&lt;/li&gt;&#10;&lt;li&gt;Lastly (and least-optimally, in Neal&amp;rsquo;s opinion) - creating a separate data&#10;package that holds the response cache&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I took a stab at each of these approaches, but one by one I had to discard them:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;API responses for fantasy football platforms are subject to change on a pretty&#10;regular basis - I&amp;rsquo;ve had to rebuild the captured responses several times since&#10;I started developing this package in August 2020, and corner cases are unlikely&#10;to appear in the first 5-10 responses of data&lt;/li&gt;&#10;&lt;li&gt;Manually pruning the captured responses is unfeasible for similar reasons as&#10;above, and is also infinitely more time-consuming!&lt;/li&gt;&#10;&lt;li&gt;I did try to refactor some of the larger/longer-running functions into&#10;smaller/more-testable units, especially since APIs frequently have options to&#10;limit and paginate. However, some of these requests were very difficult to&#10;configure in this fashion, and other functions that I wanted to test needed to&#10;aggregate large amounts of data. I wanted the tests to be easily evaluated via&#10;the naked eye.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Thus, I landed on creating a separate package of test data files as my best route&#10;forward. I briefly considered the idea of pushing the data package to CRAN - this&#10;would ensure that it would always be &amp;ldquo;accessible&amp;rdquo; to the CRAN package wherever&#10;accessed - but discarded the prospects of both getting an exemption on package&#10;size AND maintaining regular updates to the test data. Instead, I opted for a&#10;separate GitHub repository of test files, which I considered to be a relatively&#10;reliable place to store the data.&lt;/p&gt;&#10;&lt;p&gt;Of course, a few concerns popped up with this option, which needed handling:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I would need to check that I could download the data at the time of&#10;test/vignette-build, and then skip testing/evaluating if GitHub or the data&#10;were unavailable.&lt;/li&gt;&#10;&lt;li&gt;I would need to find a way to version-control and make sure that I didn&amp;rsquo;t&#10;accidentally delete test data that would cause CRAN testing to break - that&#10;would defeat the whole purpose of going through all the trouble of mocking the data!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I managed the latter concern with &lt;a href="https://github.com/dynastyprocess/ffscrapr-tests/releases/tag/1.3.0" target="_blank" rel="noreferrer"&gt;a GitHub release&lt;/a&gt;,&#10;which allowed me to version-lock the test-data to match the CRAN version. I&amp;rsquo;ll&#10;need to make sure to update the links within the package at every CRAN release,&#10;but I think this should be feasible!&lt;/p&gt;&#10;&#10; &#10; &#10; &#10; &#10;&#10;&#10;&#10;&lt;div&#10; &#10; class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"&#10; &#10; &gt;&#10; &lt;span&#10; &#10; class="text-primary-400 pe-3 flex items-center"&#10; &#10; &gt;&#10; &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/&gt;&lt;/svg&gt;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10;&#10; &lt;span&#10; &#10; class="dark:text-neutral-300"&#10; &#10; &gt;&lt;strong&gt;2023 note&lt;/strong&gt; I didn&amp;rsquo;t know of the existence of &lt;a href="https://github.com/ropensci/piggyback" target="_blank" rel="noreferrer"&gt;piggyback&lt;/a&gt;&#10;at the time of this writing but if I did I would have used it instead so as to&#10;not have to track another repository. It&amp;rsquo;s been buried on my project list, but I&amp;rsquo;ve&#10;wanted to make a PR to add a &lt;code&gt;piggyback&lt;/code&gt; option to &lt;code&gt;httptest&lt;/code&gt; for a while now.&lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h2 class="relative group"&gt;Accessing the mock data&#10; &lt;div id="accessing-the-mock-data" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#accessing-the-mock-data" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;This needed some finagling, but I eventually settled on the following code in my &lt;a href="https://github.com/dynastyprocess/ffscrapr/blob/main/tests/testthat/setup.R" target="_blank" rel="noreferrer"&gt;testthat setup file&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;suppressPackageStartupMessages&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testthat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkmate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;(Packages)&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;identical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Sys.getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;MOCK_BYPASS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;with_mock_api&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;force&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;If a system environment variable named MOCK_BYPASS has value of true, override the definition of &amp;ldquo;with_mock_api&amp;rdquo; to be &amp;ldquo;force&amp;rdquo;, which stops the mocking behaviour and replaces it with normal evaluation.&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;download_mock&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;identical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Sys.getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;MOCK_BYPASS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is.null&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;nslookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;If not mock_bypass AND github.com is available (via a curl check), set download_mock to TRUE, otherwise set to FALSE. This checks as to whether the mock data should be downloaded at all.&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="kc"&gt;FALSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;download_mock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;tryCatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;download.file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://github.com/dynastyprocess/ffscrapr-tests/archive/1.3.0.zip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;f.zip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;unzip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;f.zip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exdir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;.mockPaths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;ffscrapr-tests-1.3.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;withr&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ffscrapr-tests-1.3.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;f.zip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;recursive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;force&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;testthat&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;teardown_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;warning&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt; &lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt; &lt;span class="kc"&gt;TRUE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;With &amp;ldquo;skip&amp;rdquo; as default FALSE, try to download the data from the archive link (version controlled) and then unzip it. Then, set the .mockPaths to read this unzipped directory. When done, delete the zipped file and the test data. If anything generates a warning or error, set skip to TRUE.&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;skippy &amp;lt;- function() NULL&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if (skip) skippy &amp;lt;- function() testthat::skip(message = &amp;#34;Unable to download test data&amp;#34;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;With skippy defined by default as a function that does nothing, if skip is TRUE (because of the previous step), change the definition of skippy to skip all tests.&lt;/p&gt;&#10;&lt;p&gt;I think this addresses my concerns - if running in a non-live-situation AND mock data can be downloaded, do so, otherwise skip all tests! A similar setup and teardown happens in all the &lt;a href="https://github.com/dynastyprocess/ffscrapr/blob/main/vignettes/espn_basics.Rmd#L21" target="_blank" rel="noreferrer"&gt;vignette RMDs as well&lt;/a&gt;.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Capturing the mock data&#10; &lt;div id="capturing-the-mock-data" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#capturing-the-mock-data" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Capturing the mock data turned out to be a fairly straightforward process. I made sure to have the test-data folder in the same top-level directory as the package itself, and then capturing the mock data was comfortably done with:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-R" data-lang="R"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;start_capturing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;../ffscrapr-tests&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;stop_capturing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;I&amp;rsquo;ve had some problems with my PC&amp;rsquo;s locale settings encoding the JSON oddly in the past, but I usually get around that by jumping onto an available RStudio server instance on a Linux server and re-recording the tests there when necessary. (This is probably overkill - if you&amp;rsquo;re reading this and have better suggestions, let me know!)&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Testing the real APIs with GitHub Actions&#10; &lt;div id="testing-the-real-apis-with-github-actions" class="anchor"&gt;&lt;/div&gt;&#10; &#10; &lt;span&#10; class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;&#10; &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#testing-the-real-apis-with-github-actions" aria-label="Anchor"&gt;#&lt;/a&gt;&#10; &lt;/span&gt;&#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;CRAN obviously cannot test the real APIs, because it cannot handle failures gracefully. Instead, I have &lt;a href="https://github.com/dynastyprocess/ffscrapr/blob/main/.github/workflows/test-apis.yml" target="_blank" rel="noreferrer"&gt;a GitHub Action that does this&lt;/a&gt; by running the package tests with the MOCK_BYPASS environment variable set to &amp;ldquo;true&amp;rdquo;. Paired with a cron schedule of once a week, this successfully catches API changes and issues before users can find them, which helps me anticipate and start patching bugs more effectively.&lt;/p&gt;&#10;</description><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://tanho.ca/posts/2021-03-08_httptesting/featured.jpg"/></item></channel></rss>