<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tyler Smith</title>
    <description>The latest articles on DEV Community by Tyler Smith (@tylerlwsmith).</description>
    <link>https://dev.to/tylerlwsmith</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F68031%2F18f40970-4f6b-403a-b5bd-3f469a206080.jpeg</url>
      <title>DEV Community: Tyler Smith</title>
      <link>https://dev.to/tylerlwsmith</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tylerlwsmith"/>
    <language>en</language>
    <item>
      <title>Alma and Rocky Linux ISOs: DVD vs Boot vs Minimal</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Mon, 06 Apr 2026 02:43:41 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/alma-and-rocky-isos-dvd-vs-boot-vs-minimal-hi5</link>
      <guid>https://dev.to/tylerlwsmith/alma-and-rocky-isos-dvd-vs-boot-vs-minimal-hi5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This article was written in April 2026. If you are reading this in the distant future, things have likely changed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both AlmaLinux and Rocky Linux come with three ISO options for download: &lt;strong&gt;DVD ISO&lt;/strong&gt;, &lt;strong&gt;Boot ISO&lt;/strong&gt;, and &lt;strong&gt;Minimal ISO&lt;/strong&gt;. Neither the &lt;a href="https://wiki.almalinux.org/documentation/installation-guide.html#download-almalinux-iso" rel="noopener noreferrer"&gt;AlmaLinux docs&lt;/a&gt; or the &lt;a href="https://wiki.rockylinux.org/rocky/image/#notes-about-what-does-each-iso-do" rel="noopener noreferrer"&gt;Rocky Linux docs&lt;/a&gt; do a great job explaining the differences between these ISOs. Posts on the &lt;a href="https://forums.rockylinux.org/t/minimal-or-boot/7019/2" rel="noopener noreferrer"&gt;Rocky Linux forms&lt;/a&gt; and &lt;a href="https://www.reddit.com/r/AlmaLinux/comments/ux8xwh/boot_minimal_or_dvd_and_manifest_version/" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt; elaborate beyond the docs, but still don't do a great job explaining &lt;em&gt;exactly&lt;/em&gt; what each ISO contains or when you'd use each.&lt;/p&gt;

&lt;p&gt;This article will explain the similarities and differences between the three ISOs, and will provide a recommendation of which ISO you should use.&lt;/p&gt;

&lt;h2&gt;
  
  
  DVD ISO
&lt;/h2&gt;

&lt;p&gt;The DVD ISO is an &lt;code&gt;~8gb&lt;/code&gt; image that contains all packages for a full offline installation. &lt;/p&gt;

&lt;p&gt;By default, the boot ISO will install AlmaLinux or Rocky Linux with server packages and a full GUI via the Gnome desktop environment. To change this, navigate to the &lt;strong&gt;Software Selection&lt;/strong&gt; screen during the installation where you can choose from the following options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Server with GUI.&lt;/strong&gt; This installs server packages along with a Gnome desktop environment for easy point-and-click management. It installs the software included with &lt;code&gt;dnf&lt;/code&gt;'s "Server with GUI" environment group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server.&lt;/strong&gt; This contains all the packages you would expect a distribution to have for managing a server via the CLI. It installs the software included with &lt;code&gt;dnf&lt;/code&gt;'s "Server" environment group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Install.&lt;/strong&gt; This gives you a bare-bones Linux experience that excludes any packages that aren't absolutely necessary. Everyday commands like &lt;code&gt;nano&lt;/code&gt;, &lt;code&gt;vim&lt;/code&gt;, and &lt;code&gt;which&lt;/code&gt; are excluded, allowing the user to manually install only the software they need when they need it. It installs the software included with &lt;code&gt;dnf&lt;/code&gt;'s "Minimal Install" environment group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Operating System.&lt;/strong&gt; I &lt;em&gt;believe&lt;/em&gt; this lets you select the exact packages you want to be installed on the OS. I'm not actually sure how this works because I haven't tried it 🤷‍♂️&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;To see what &lt;code&gt;dnf&lt;/code&gt; groups are available&lt;/strong&gt;, run the following command on a RHEL-like distribution (Alma, Rocky, CentOS Stream, RHEL).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dnf group list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output a list like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Available Environment Groups:
   Server with GUI
   Minimal Install
   Custom Operating System
Installed Environment Groups:
   Server
Installed Groups:
   Container Management
   Headless Management
Available Groups:
   Legacy UNIX Compatibility
   Smart Card Support
   Console Internet Tools
   Development Tools
   .NET Development
   Graphical Administration Tools
   Network Servers
   RPM Development Tools
   Scientific Support
   Security Tools
   System Tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;To see what software is included in a group (for example, the "Minimal Install" environment group)&lt;/strong&gt;, run the command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dnf group info &lt;span class="s2"&gt;"Minimal Install"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Environment Group: Minimal Install
 Description: Basic functionality.
 Mandatory Groups:
   Core
 Optional Groups:
   Standard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can drill into these further to see the actual included packages using &lt;code&gt;dnf group info "Core"&lt;/code&gt; and &lt;code&gt;dnf group info "Standard"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To install software from a listed group&lt;/strong&gt; (for example, if you did the "Minimal Install" but want software from "Server"), run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf group &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"Server"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can learn more about DNF groups in OneUptime's article &lt;a href="https://oneuptime.com/blog/post/2026-03-04-manage-package-groups-environment-groups-dnf-rhel-9/view" rel="noopener noreferrer"&gt;How to Manage Package Groups and Environment Groups with DNF on RHEL&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boot ISO
&lt;/h2&gt;

&lt;p&gt;The Boot ISO is a &lt;code&gt;~1gb&lt;/code&gt; image that requires an Internet connection during the installation to download packages. It includes all of the same options as the DVD ISO (such as &lt;strong&gt;Server with GUI&lt;/strong&gt;, &lt;strong&gt;Server&lt;/strong&gt;, and &lt;strong&gt;Minimal Install&lt;/strong&gt; within the "Software Selection" installation screen), but since the packages are downloaded during the installation process it allows the ISO image to be significantly smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimal ISO
&lt;/h2&gt;

&lt;p&gt;The Minimal ISO is a &lt;code&gt;~2gb&lt;/code&gt; image made for fully offline installation. Unlike the DVD ISO or Boot ISO, the only option on the &lt;strong&gt;Software Selection&lt;/strong&gt; installation screen is &lt;strong&gt;Minimal Install&lt;/strong&gt; (i.e., no &lt;strong&gt;Server with GUI&lt;/strong&gt;, &lt;strong&gt;Server&lt;/strong&gt;, or &lt;strong&gt;Custom Operating System&lt;/strong&gt; options).&lt;/p&gt;

&lt;p&gt;Though the Minimal ISO can be installed without a network connection, you will almost certainly need an Internet connection immediately after the installation to install packages like text editors (&lt;code&gt;nano&lt;/code&gt;, &lt;code&gt;vim&lt;/code&gt;) that make an operating system useful. This makes the promise of a fully offline installation fall short as a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which ISO should you download?
&lt;/h2&gt;

&lt;p&gt;In most cases, the &lt;strong&gt;Boot ISO&lt;/strong&gt; will probably make the most sense: it has the smallest initial download size, and your packages will be up-to-date when you first boot the operating system.&lt;/p&gt;

&lt;p&gt;If you were to install via the &lt;strong&gt;DVD ISO&lt;/strong&gt;, you'd need to update almost all of the packages immediately after installation, negating the benefits of the fully offline installation. I'd personally only consider this option if I were installing an operating system on a server that cannot be connected to the Internet.&lt;/p&gt;

&lt;p&gt;In most cases, I'd avoid the &lt;strong&gt;Minimal ISO&lt;/strong&gt; unless you want to build the leanest server possible by manually installing only the software that you absolutely need.&lt;/p&gt;




&lt;p&gt;Let me know if you found this article helpful. If I got anything wrong, let me know in the comments and I'll correct the article.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>almalinux</category>
      <category>rockylinux</category>
    </item>
    <item>
      <title>OpenStreetMap's software ecosystem and tools</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Fri, 24 Oct 2025 00:56:15 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/openstreetmaps-software-ecosystem-and-tools-m09</link>
      <guid>https://dev.to/tylerlwsmith/openstreetmaps-software-ecosystem-and-tools-m09</guid>
      <description>&lt;p&gt;&lt;a href="https://wiki.openstreetmap.org/wiki/About_OpenStreetMap" rel="noopener noreferrer"&gt;OpenStreetMap's Wiki&lt;/a&gt; defines OpenStreetMap (OSM) as "a free, editable map of the whole world that is being built by volunteers largely from scratch and released with an open-content license." In some ways, it's like an open source &amp;amp; open data version of Google Maps. Except unlike Google maps, it is not a cohesive product that provides countless capabilities out-of-the-box. OpenStreetMap is a vast ecosystem of open source packages created by small teams of disparate maintainers building pluggable components that are powered by a multitude of technologies and programming languages. While this makes it far less cohesive than a product like Google maps, it makes it more powerful and customizable.&lt;/p&gt;

&lt;p&gt;This article will look at various pieces of the OpenStreetMap ecosystem. OSM information is spread across many sites and hard-to-find links: this is an attempt to consolidate much of that information in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  openstreetmap.org
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.openstreetmap.org/" rel="noopener noreferrer"&gt;OpenStreetMap's website&lt;/a&gt; allows users to view &amp;amp; edit OpenStreetMap data. You can think of it as Wikipedia for maps. Built on top of Ruby on Rails, the site is the central repository for OpenStreetMap data that all other tools in the OSM ecosystem rely upon. The website is also a miniature showcase for OSM capabilities like map rendering, theming (available in the "layers" panel), and directions (which allows the user to pick from multiple routing services). &lt;/p&gt;

&lt;p&gt;Most users will not consume OpenStreetMap's capabilities through its website: the data provided by the website is intended to be consumed by application developers building their own map-based applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenStreetMap data
&lt;/h2&gt;

&lt;p&gt;OpenStreetMap's user-contributed data is the core of its ecosystem. With almost all of the software surrounding OSM being composed of interchangeable pieces, the data is OSM's defining quality.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://planet.openstreetmap.org/" rel="noopener noreferrer"&gt;planet.openstreetmap.org&lt;/a&gt; subdomain provides weekly exports of the entire planet's OpenStreetMap data. It is provided as both compressed XML files and a custom Protobuf format which uses a &lt;code&gt;.osm.pbf&lt;/code&gt; extension. At the time of writing, the compressed XML files are a little over 150gb, and they uncompress to &lt;a href="https://wiki.openstreetmap.org/wiki/Downloading_data#All_data_at_once" rel="noopener noreferrer"&gt;over 2 TB according to the Wiki&lt;/a&gt;. The &lt;code&gt;.osm.pbf&lt;/code&gt; Protobuf files are a little more than half the size, with the most recent being a little more than 80gb at the time of writing. In addition to being smaller, &lt;a href="https://osm2pgsql.org/doc/faq.html#why-is-osm2pgsql-so-slow" rel="noopener noreferrer"&gt;&lt;code&gt;.osm.pbf&lt;/code&gt; can also be processed faster&lt;/a&gt; so they're generally the more desirable format.&lt;/p&gt;

&lt;p&gt;Because the complete Planet dataset is so large, OSM also offers much smaller weekly changesets that can be applied to an existing planet export on a rolling basis to keep it in sync.&lt;/p&gt;

&lt;p&gt;The exported data is typically imported into a PostgreSQL database with the PostGIS extension enabled. The data is typically imported into PostgreSQL using &lt;a href="https://osm2pgsql.org/" rel="noopener noreferrer"&gt;osm2pgsql&lt;/a&gt;. The full Planet data can exceed a terabyte when imported into Postgres (&lt;a href="https://github.com/mediagis/nominatim-docker/discussions/265#discussioncomment-6208558" rel="noopener noreferrer"&gt;source&lt;/a&gt;, &lt;a href="https://gis.stackexchange.com/a/421338/329907" rel="noopener noreferrer"&gt;source&lt;/a&gt;, &lt;a href="https://wiki.openstreetmap.org/wiki/Osm2pgsql/benchmarks" rel="noopener noreferrer"&gt;source&lt;/a&gt;), and the import process can take hours or days depending on your &lt;a href="https://wiki.openstreetmap.org/wiki/Osm2pgsql/benchmarks" rel="noopener noreferrer"&gt;hardware&lt;/a&gt;, &lt;a href="https://osm2pgsql.org/doc/manual.html#tuning-the-postgresql-server" rel="noopener noreferrer"&gt;Postgres optimizations&lt;/a&gt;, and &lt;a href="https://osm2pgsql.org/doc/manual.html#flat-node-store" rel="noopener noreferrer"&gt;import flags&lt;/a&gt;. OSM's &lt;a href="https://wiki.openstreetmap.org/wiki/Osmosis" rel="noopener noreferrer"&gt;Osmosis&lt;/a&gt; tool can shrink the size of the data by extracting only the data that you're interested in, such as data inside of a bounding box, or extracting only the speed cameras. The OSM Wiki contains a list of &lt;a href="https://wiki.openstreetmap.org/wiki/Downloading_data#Huge_amounts_of_data" rel="noopener noreferrer"&gt;other data extraction tools&lt;/a&gt; to reduce the dataset to the things a developer is interested in.&lt;/p&gt;

&lt;p&gt;The most straight forward way to keep a dataset small &amp;amp; an import fast is to download a dataset of a single geographic area. OpenStreetMap's does not provide geographic extracts, but sites like &lt;a href="https://download.openstreetmap.fr/" rel="noopener noreferrer"&gt;download.openstreetmap.fr&lt;/a&gt;, &lt;a href="https://download.geofabrik.de/" rel="noopener noreferrer"&gt;Geofabrik&lt;/a&gt;, and &lt;a href="https://download.bbbike.org/osm/" rel="noopener noreferrer"&gt;BBBike&lt;/a&gt; offer regularly-updated geographic extracts. You can find more extract sources on the OSM wiki's &lt;a href="https://wiki.openstreetmap.org/wiki/Planet.osm#Extracts" rel="noopener noreferrer"&gt;Planet.osm page&lt;/a&gt;, &lt;a href="https://wiki.openstreetmap.org/wiki/Processed_data_providers" rel="noopener noreferrer"&gt;Processed data providers page&lt;/a&gt;, and &lt;a href="https://switch2osm.org/serving-tiles/" rel="noopener noreferrer"&gt;Switch2OSM's Serving Tiles page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering an OpenStreetMap on the web
&lt;/h2&gt;

&lt;p&gt;To view an OSM map, you need a &lt;strong&gt;map library&lt;/strong&gt; and a &lt;strong&gt;tile server&lt;/strong&gt;. Map libraries create interactive maps using tiles provided by a tile server. Tile servers take OpenStreetMap data and render pieces of a map into individual squares that can be used by a map library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Map libraries
&lt;/h3&gt;

&lt;p&gt;Unlike commercial products like Google Maps, OpenStreetMap does not have an "official" map library that you are required to use. Among the most popular OSM map libraries for the web are &lt;a href="https://leafletjs.com/" rel="noopener noreferrer"&gt;Leaflet&lt;/a&gt;, which is the default map viewer on &lt;a href="https://www.openstreetmap.org/" rel="noopener noreferrer"&gt;openstreetmap.org&lt;/a&gt;, and &lt;a href="https://openlayers.org/" rel="noopener noreferrer"&gt;OpenLayers&lt;/a&gt;, which is considered more powerful but has a steeper learning curve. Alternatives like &lt;a href="https://maplibre.org/" rel="noopener noreferrer"&gt;MapLibre&lt;/a&gt; have SDKs for web, Android, and iOS. Other popular map libraries can be found on the &lt;a href="https://wiki.openstreetmap.org/wiki/Deploying_your_own_Slippy_Map#JavaScript_libraries" rel="noopener noreferrer"&gt;OSM wiki&lt;/a&gt; and &lt;a href="https://github.com/osmlab/awesome-openstreetmap?tab=readme-ov-file#maps" rel="noopener noreferrer"&gt;Awesome OSM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tiles consumed by these map libraries can be &lt;strong&gt;raster tiles&lt;/strong&gt; (images made of pixels), or &lt;strong&gt;vector tiles&lt;/strong&gt; (comprised of geometries and metadata). While raster tiles can generally be used by any map library that supports them (like Leaflet), there are competing &lt;a href="https://wiki.openstreetmap.org/wiki/Vector_tiles#Tile_schemas" rel="noopener noreferrer"&gt;schemas for vector tiles&lt;/a&gt;, and a vector map library might not support all vector tile schemas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Raster tiles
&lt;/h3&gt;

&lt;p&gt;OpenStreetMap officially maintains 2 first-party raster tile server packages called &lt;a href="https://github.com/openstreetmap/mod_tile" rel="noopener noreferrer"&gt;mod_tile&lt;/a&gt; and &lt;a href="https://github.com/openstreetmap/tirex" rel="noopener noreferrer"&gt;Tirex&lt;/a&gt;. The rendering for these tools is powered by the open source &lt;a href="https://mapnik.org/" rel="noopener noreferrer"&gt;Mapnik&lt;/a&gt; map generation library. Mapnik requires styles to be provided via an XML config, and a CSS-like language called &lt;a href="https://github.com/mapbox/carto" rel="noopener noreferrer"&gt;CartoCSS&lt;/a&gt; was created by Mapbox that compiles down to Mapnik configs to make building these XML configs easier. &lt;a href="https://blog.mapbox.com/the-end-of-cartocss-da2d7427cf1" rel="noopener noreferrer"&gt;CartoCSS was phased out by Mapbox starting in 2016&lt;/a&gt; in favor of their vector tiles. Despite CartoCSS &lt;a href="https://github.com/mapbox/carto/commits/master/" rel="noopener noreferrer"&gt;not receiving a commit since 2020&lt;/a&gt; and &lt;a href="https://github.com/mapbox/carto" rel="noopener noreferrer"&gt;the GitHub project being archived in 2024&lt;/a&gt;, the OpenStreetMap website still uses it for defining its Mapnik styles. A project called &lt;a href="https://github.com/gravitystorm/openstreetmap-carto" rel="noopener noreferrer"&gt;openstreetmap-carto&lt;/a&gt; maintains the stylesheets for the standard map layer on openstreetmap.org. To see more about how these pieces fit together, check the &lt;a href="https://wiki.openstreetmap.org/wiki/Component_overview" rel="noopener noreferrer"&gt;component overview page&lt;/a&gt; on the OSM wiki.&lt;/p&gt;

&lt;p&gt;Despite being first-party, these components are not required and can be substituted with various open source packages (&lt;a href="https://github.com/consbio/mbtileserver" rel="noopener noreferrer"&gt;mbtileserver&lt;/a&gt;, &lt;a href="https://github.com/maptiler/tileserver-gl" rel="noopener noreferrer"&gt;TileServer GL&lt;/a&gt;, etc.) or commercial tile servers as a service (&lt;a href="https://www.mapbox.com/" rel="noopener noreferrer"&gt;Mapbox&lt;/a&gt;, &lt;a href="https://stadiamaps.com/" rel="noopener noreferrer"&gt;Stadia Maps&lt;/a&gt;, etc.). As long as raster tile servers follow the &lt;a href="https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames" rel="noopener noreferrer"&gt;filename and URL path conventions&lt;/a&gt;, they are interchangeable. You can find additional &lt;a href="https://wiki.openstreetmap.org/wiki/Tile_servers" rel="noopener noreferrer"&gt;tile server packages&lt;/a&gt; on the OSM Wiki. &lt;/p&gt;

&lt;p&gt;OpenStreetMap provides a free-of-charge raster tile server at &lt;code&gt;https://tile.openstreetmap.org/&lt;/code&gt; that is ideal for developers to learn to build maps without having to set up their own tile server or pay a third-party tile provider. To keep it free for everyone, users must abide by its &lt;a href="https://operations.osmfoundation.org/policies/tiles/" rel="noopener noreferrer"&gt;tile usage policy&lt;/a&gt; which prohibits bulk downloads of tiles and &lt;code&gt;no-cache&lt;/code&gt; headers. Since this tile server provides no uptime guarantees, commercial apps should strongly consider using a &lt;a href="https://wiki.openstreetmap.org/wiki/Raster_tile_providers" rel="noopener noreferrer"&gt;paid raster tile provider&lt;/a&gt; or &lt;a href="https://switch2osm.org/serving-tiles/#System-requirements" rel="noopener noreferrer"&gt;hosting their own raster tile server&lt;/a&gt;. If you are considering hosting your own tile server for a heavy production workload, the OSM wiki has &lt;a href="https://wiki.openstreetmap.org/wiki/Servers/Tile_Rendering#Tile_rendering_node" rel="noopener noreferrer"&gt;the specs that they require for running their own production tiling servers&lt;/a&gt;. It's worth noting that not every single tile needs to be pre-rendered by the tile server: &lt;a href="https://wiki.openstreetmap.org/w/index.php?title=Tile_disk_usage&amp;amp;oldid=2035788" rel="noopener noreferrer"&gt;a recently removed Tile disk usage page on the Wiki&lt;/a&gt; estimates that pre-rendering every tile would take up 54tb, and less than 2% of all total tiles are actually viewed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vector tiles
&lt;/h3&gt;

&lt;p&gt;OpenStreetMap does not officially maintain any first-party vector tile server packages, but several open source packages exist for generating and serving tiles (&lt;a href="https://tilemaker.org/" rel="noopener noreferrer"&gt;tilemaker&lt;/a&gt;, &lt;a href="https://github.com/consbio/mbtileserver" rel="noopener noreferrer"&gt;mbtileserver&lt;/a&gt;, &lt;a href="https://martin.maplibre.org/" rel="noopener noreferrer"&gt;Martin&lt;/a&gt;, etc.), along with several &lt;a href="https://wiki.openstreetmap.org/wiki/Vector_tiles#Providers" rel="noopener noreferrer"&gt;tile providers as a service&lt;/a&gt;. Vector Tiles were a project that originally came out of commercial map provider &lt;a href="https://www.mapbox.com/" rel="noopener noreferrer"&gt;Mapbox&lt;/a&gt; to enable new map capabilities, like rotating a map while maintaining the text orientation. You can read more about the history of vector maps on &lt;a href="https://www.openstreetmap.org/user/daniel-j-h/diary/404061" rel="noopener noreferrer"&gt;daniel-j-h's blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Mapbox created a format called &lt;a href="https://github.com/mapbox/vector-tile-spec" rel="noopener noreferrer"&gt;&lt;strong&gt;Mapbox Vector Tiles&lt;/strong&gt;&lt;/a&gt; (MVT) based on Google's Protobuf. It is the defacto industry standard for vector tiles and has a &lt;code&gt;.mtv&lt;/code&gt; extension. These tiles can have different schemas (data included inside of the MVT tile). If MVT is like a CSV file, schemas are like the column names. Popular vector tile schemas include &lt;a href="https://shortbread-tiles.org/schema/1.0/" rel="noopener noreferrer"&gt;Shortbread&lt;/a&gt; and &lt;a href="https://openmaptiles.org/schema/" rel="noopener noreferrer"&gt;OpenMapTiles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;OpenStreetMap provides a free-of-charge Shortbread vector tile server at &lt;code&gt;https://vector.openstreetmap.org&lt;/code&gt; that is ideal for developers to learn to build maps without having to set up their own tile server or pay a third-party tile provider. Like OSM's raster tile server, users must abide by its &lt;a href="https://operations.osmfoundation.org/policies/vector/" rel="noopener noreferrer"&gt;tile usage policy&lt;/a&gt; which prohibits bulk downloads of tiles and &lt;code&gt;no-cache&lt;/code&gt; headers. You can see a demo of their vector tile server &lt;a href="https://vector.openstreetmap.org/demo/shortbread/#1/0/0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To learn more about vector tiles, visit the &lt;a href="https://wiki.openstreetmap.org/wiki/Vector_tiles" rel="noopener noreferrer"&gt;OSM wiki's Vector tiles page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static maps
&lt;/h3&gt;

&lt;p&gt;Alternatively, it is possible to render a static map to an image instead of using a map library for an interactive map (similar to &lt;a href="https://developers.google.com/maps/documentation/maps-static/overview" rel="noopener noreferrer"&gt;Google Maps Static API&lt;/a&gt;). The &lt;a href="https://wiki.openstreetmap.org/wiki/Static_map_images" rel="noopener noreferrer"&gt;OSM wiki static map images page&lt;/a&gt; provides a list of services and software packages to enable this functionality, but if you're already using Mapnik, it may be simplest to accomplish this using its &lt;a href="https://mapnik.org/docs/v2.2.0/api/python/mapnik._mapnik-module.html#render" rel="noopener noreferrer"&gt;Node&lt;/a&gt; or &lt;a href="https://mapnik.org/documentation/node-mapnik/3.5/#example-31" rel="noopener noreferrer"&gt;Python&lt;/a&gt; bindings.&lt;/p&gt;

&lt;p&gt;To learn more about rendering raster, vector, or static maps, visit the &lt;a href="*%20https://wiki.openstreetmap.org/wiki/Rendering"&gt;OSM wiki's Rendering page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Geocoding
&lt;/h2&gt;

&lt;p&gt;Geocoding is the process of taking the name or address of a place and returning its longitude and latitude.&lt;/p&gt;

&lt;p&gt;OpenStreetMap maintains a Python-based open source project called &lt;a href="https://nominatim.org/" rel="noopener noreferrer"&gt;Nominatim&lt;/a&gt; for geocoding with OpenStreetMap. Nominatim uses osm2pgsql to import its data. Unfortunately, tile servers and Nominatim cannot share a database: their database schemas are incompatible. Nominatim also supports reverse geocoding, which takes a longitude and latitude and returns what is at that location.&lt;/p&gt;

&lt;p&gt;OpenStreetMap runs a &lt;a href="https://operations.osmfoundation.org/policies/nominatim/" rel="noopener noreferrer"&gt;free-to-use Nominatim server&lt;/a&gt;, but the permitted usage is restricted: an absolute maximum of 1 request per second. Additionally, clients repeatedly sending the same query may be blocked. Because of this, you may want to consider self-hosting or using one of the commercial providers listed on the &lt;a href="https://wiki.openstreetmap.org/wiki/Geocoding" rel="noopener noreferrer"&gt;OSM wiki's Geocoding page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routing
&lt;/h2&gt;

&lt;p&gt;Routing creates directions to get from point A to point B, much like Google Maps' directions. There are different routing tools for cars, bikes, and more.&lt;/p&gt;

&lt;p&gt;While OpenStreetMap does not maintain any first-party routing tools, its website features &lt;a href="https://project-osrm.org/" rel="noopener noreferrer"&gt;OSRM&lt;/a&gt;, &lt;a href="https://www.graphhopper.com/" rel="noopener noreferrer"&gt;GraphHopper&lt;/a&gt;, and &lt;a href="https://github.com/valhalla/valhalla" rel="noopener noreferrer"&gt;Valhalla&lt;/a&gt;. Additional routing software can be found on the &lt;a href="https://wiki.openstreetmap.org/wiki/Routing" rel="noopener noreferrer"&gt;OSM wiki's Routing page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenStreetMap's license
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: I am not a lawyer and this is not legal advice. The following section is my personal understanding of the ODbL, which may be incorrect. Do not take my interpretation as a statement of fact.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The OpenStreetMap data is covered by the Open Data Commons &lt;a href="https://opendatacommons.org/licenses/odbl/1-0/" rel="noopener noreferrer"&gt;Open Database License (ODbL)&lt;/a&gt;. From my reading and limited understanding, it feels like the GPL for data, but with slightly less viral copy-left enforcement. You can use the data for any purpose (including commercial), but you must attribute the source. You may create a Derivative Database by modifying or extracting data, but you must make the Derivative Database available, and that database must attribute the original source. However, things created with the Database or Derivative Database (Produced Works) may be licensed under different terms than the OBbL as long as the previously listed obligations have been met.&lt;/p&gt;

&lt;p&gt;An interesting part of the ODbL is the idea of a Collective Database, where the data from the ODbL database is unmodified but combined with data from other independent datasets that are assembled into a collective whole. A Collective Database is not considered a Derivative Database, therefore included non-OBdL datasets that are used in conjunction with OBdL Databases are not subject to the requirements of the OBdL. We can see this in action in &lt;a href="https://gspe21-ssl.ls.apple.com/html/attribution-311.html" rel="noopener noreferrer"&gt;Apple Maps' data acknowledgement page&lt;/a&gt;, where they list OpenStreetMap contributors first, then list many commercial and listings below.&lt;/p&gt;

&lt;p&gt;The Open Data Commons site includes &lt;a href="https://opendatacommons.org/licenses/odbl/summary/" rel="noopener noreferrer"&gt;a human readable summary of the ODbL 1.0 license&lt;/a&gt;, but I'd recommend reading the full license and consulting a lawyer if you have questions. Certainly take everything I've said with a grain of salt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attribution requirements
&lt;/h2&gt;

&lt;p&gt;Things created with OpenStreetMap must attribute OpenStreetMap and make clear that the data is covered by the OBdL license. For maps, this is typically done by putting a hyperlink that says OpenStreetMap in the map's bottom right hand corner and linking to the &lt;a href="https://www.openstreetmap.org/copyright" rel="noopener noreferrer"&gt;OSM Copyright page&lt;/a&gt;, which has information about OpenStreetMap’s data sources as well as the ODbL. The OpenStreetMap Foundation has a &lt;a href="https://osmfoundation.org/wiki/Licence/Attribution_Guidelines" rel="noopener noreferrer"&gt;Licence/Attribution Guidelines page&lt;/a&gt; on their website that discusses guidelines for other Produced Works such as static maps and routing engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;The best place to start your journey with OpenStreetMap development is the &lt;a href="https://switch2osm.org/" rel="noopener noreferrer"&gt;Switch2OSM website&lt;/a&gt;. It discusses the &lt;a href="https://switch2osm.org/the-basics/" rel="noopener noreferrer"&gt;fundamentals of OSM&lt;/a&gt; in much simpler terms than this article, and has instructions for &lt;a href="https://switch2osm.org/using-tiles/" rel="noopener noreferrer"&gt;getting started with tiles&lt;/a&gt; using Leaflet, OpenLayers, and MapLibre. Switch2OSM also has tutorials for &lt;a href="https://switch2osm.org/serving-tiles/" rel="noopener noreferrer"&gt;setting up your own tile server&lt;/a&gt; on Docker, Debian, and Ubuntu, but for many use cases developers can use OpenStreetMap's free tile servers and not have to set up their own.&lt;/p&gt;




&lt;p&gt;I hope you found this post helpful. Please comment if you find incorrect information: I've been exploring the OpenStreetMap ecosystem for about a week so my understanding is limited. This post was an attempt to organize everything I've learned and keep track of links to every page I found helpful in shaping my understanding OSM.&lt;/p&gt;

</description>
      <category>openstreetmap</category>
      <category>osm</category>
      <category>gis</category>
    </item>
    <item>
      <title>Absolute paths for assets in iOS &amp; Android Cordova apps</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Fri, 31 Jan 2025 04:36:23 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/absolute-paths-for-assets-in-ios-android-cordova-apps-mn8</link>
      <guid>https://dev.to/tylerlwsmith/absolute-paths-for-assets-in-ios-android-cordova-apps-mn8</guid>
      <description>&lt;p&gt;There's an abundance of outdated information that states you can only use relative paths for assets in Cordova mobile apps. That information is incorrect: you can use absolute paths for assets in Cordova by configuring the &lt;code&gt;scheme&lt;/code&gt; and &lt;code&gt;hostname&lt;/code&gt; within &lt;code&gt;config.xml&lt;/code&gt;. These configuration settings have been in Cordova for several years, and they are mentioned in the June 2020 &lt;a href="https://cordova.apache.org/announcements/2020/06/01/cordova-ios-release-6.0.0.html" rel="noopener noreferrer"&gt;Cordova version 6 release notes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's what the &lt;a href="https://cordova.apache.org/docs/en/12.x/config_ref/#preference" rel="noopener noreferrer"&gt;Cordova documentation&lt;/a&gt; says about &lt;code&gt;scheme&lt;/code&gt; and &lt;code&gt;hostname&lt;/code&gt;: &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Attributes&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;
        &lt;strong&gt;scheme&lt;/strong&gt;
        &lt;br&gt;
        &lt;code&gt;String&lt;/code&gt;
        &lt;br&gt;
        Android &amp;amp; iOS
      &lt;/td&gt;
      &lt;td&gt;
        &lt;em&gt;Default:&lt;/em&gt;
        &lt;ul&gt;
          &lt;li&gt;Android: &lt;code&gt;https&lt;/code&gt;
&lt;/li&gt;
          &lt;li&gt;
            iOS: &lt;em&gt;not defined&lt;/em&gt;, but falls back to &lt;code&gt;app&lt;/code&gt; if the
            value is invalid.
          &lt;/li&gt;
        &lt;/ul&gt;
        &lt;br&gt;
        &lt;em&gt;Allowed values:&lt;/em&gt;
        &lt;ul&gt;
          &lt;li&gt;Android: &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt;
&lt;/li&gt;
          &lt;li&gt;
            iOS: Any non-reserved schemes. (&lt;a href="https://github.com/WebKit/WebKit/blob/ba2a851809a33013068ec8511883055cabd239be/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKURLSchemeHandler-1.mm#L222-L244" rel="noopener noreferrer"&gt;Example Reserved Schemes&lt;/a&gt;)
          &lt;/li&gt;
        &lt;/ul&gt;
        &lt;br&gt;
        This property contains the scheme which your app content is served from.
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;
        &lt;strong&gt;hostname&lt;/strong&gt;
        &lt;br&gt;
        &lt;code&gt;String&lt;/code&gt;
        &lt;br&gt;Android &amp;amp; iOS
      &lt;/td&gt;
      &lt;td&gt;
        &lt;em&gt;Default:&lt;/em&gt; &lt;code&gt;localhost&lt;/code&gt;
        &lt;br&gt;
        &lt;br&gt;
        This property contains the hostname which the app content is served
        from.
        &lt;br&gt;
        &lt;br&gt;
        If the preference &lt;code&gt;scheme&lt;/code&gt; is not defined for iOS, the
        &lt;code&gt;hostname&lt;/code&gt; value will be ignored.
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;scheme&lt;/code&gt; and &lt;code&gt;hostname&lt;/code&gt; within &lt;code&gt;config.xml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You can set the &lt;code&gt;scheme&lt;/code&gt; and &lt;code&gt;hostname&lt;/code&gt; preferences within the respective platform blocks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;widget&lt;/span&gt;
    &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/ns/widgets"&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:cdv=&lt;/span&gt;&lt;span class="s"&gt;"http://cordova.apache.org/ns/1.0"&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"io.cordova.hellocordova"&lt;/span&gt;
    &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0.0"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;platform&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;preference&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"scheme"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"https"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;preference&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"hostname"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;platform&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ios"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;preference&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"scheme"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;preference&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"hostname"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/platform&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Other config settings... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/widget&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, you can load your assets using absolute paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- stylesheet --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/assets/style.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- script --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/assets/app.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- image --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"DEV example"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/assets/example.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Compatibility
&lt;/h2&gt;

&lt;p&gt;With the browser platform, absolute paths are supported by the web platform itself: schemes and hostnames are built-in. However, Cordova's Electron target does not support &lt;code&gt;scheme&lt;/code&gt; or &lt;code&gt;hostname&lt;/code&gt;, so Electron Cordova apps must load assets using relative paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did Cordova always work like this?
&lt;/h2&gt;

&lt;p&gt;Cordova has not always supported &lt;code&gt;scheme&lt;/code&gt; and &lt;code&gt;hostname&lt;/code&gt; on all mobile platforms, but it was added to &lt;a href="https://github.com/WebView-CG/usage-and-challenges/issues/7#issuecomment-1150155241" rel="noopener noreferrer"&gt;better support CORS&lt;/a&gt; and simplify asset loading. Before these settings were available, Cordova used &lt;code&gt;file://&lt;/code&gt; paths like the one below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;file:///Users/tyler/Library/Developer/CoreSimulator/Devices/AFB24668-7341-4D77-9C7D-D75EF6FF602D/data/Containers/Bundle/Application/435E23A3-9004-4160-87F3-13A3895F734C/HelloWorld.app/www/index.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;scheme&lt;/code&gt; and &lt;code&gt;hostname&lt;/code&gt; set, the URL becomes much shorter:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app://localhost/index.html&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the file path
&lt;/h2&gt;

&lt;p&gt;If you would like to get an asset's &lt;code&gt;file://&lt;/code&gt; path, the first-party &lt;a href="https://cordova.apache.org/docs/en/12.x/reference/cordova-plugin-file/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;cordova-plugin-file&lt;/code&gt;&lt;/a&gt; plugin allows you to access the absolute file path from within the application's JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cordova&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;applicationDirectory&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www/assets/img.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Other resources
&lt;/h2&gt;

&lt;p&gt;To learn more about Cordova's approach to handling locally hosted content, check out &lt;a href="https://github.com/WebView-CG/usage-and-challenges/issues/7" rel="noopener noreferrer"&gt;this GitHub issue&lt;/a&gt; on the W3C WebView Community Group on the usage-and-challenges repo.&lt;/p&gt;




&lt;p&gt;I hope this post helped you. Please drop a comment if I got anything wrong and I'll get it updated.&lt;/p&gt;

</description>
      <category>cordova</category>
      <category>mobile</category>
      <category>ios</category>
      <category>android</category>
    </item>
    <item>
      <title>How to hold apps for manual release on Google Play Console and Apple's App Store Connect</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Tue, 28 Jan 2025 03:09:09 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/how-to-hold-apps-for-manual-release-on-google-play-console-and-app-store-connect-3dj9</link>
      <guid>https://dev.to/tylerlwsmith/how-to-hold-apps-for-manual-release-on-google-play-console-and-app-store-connect-3dj9</guid>
      <description>&lt;p&gt;Sometimes you want to submit mobile apps for review, but you want to manually control when they are released. This can be helpful if you have a new feature that you want to release on Android and iOS around the same time, but your app doesn't have any kind of feature flagging. This article will walk you through how to do that in both Google Play Console and App Store Connect.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This article was written in January 2025. If you are reading this article in the distant future, details have likely changed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Google Play Console (Android)
&lt;/h2&gt;

&lt;p&gt;On Google Play Console, manual releases (called &lt;strong&gt;"managed publishing"&lt;/strong&gt; on the platform) is an app-level setting that applies to all releases for that app.&lt;/p&gt;

&lt;p&gt;After selecting the app in Google Play Console, navigate to &lt;strong&gt;Publishing overview&lt;/strong&gt; in the sidebar, then click &lt;strong&gt;Turn on managed publishing.&lt;/strong&gt; Confirm the changes in the modal, then click &lt;strong&gt;Save.&lt;/strong&gt; This will hold all approved app changes until the developer manually releases them.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Store Connect (iOS)
&lt;/h2&gt;

&lt;p&gt;On App Store Connect, manual releases is a setting that is configured on each release version of the app.&lt;/p&gt;

&lt;p&gt;After creating a new version of the app, scroll down to the section titled &lt;strong&gt;App Store Version Release&lt;/strong&gt; and select &lt;strong&gt;Manually release this version&lt;/strong&gt;. This will hold that specific approved app version until the developer manually releases it.&lt;/p&gt;




&lt;p&gt;Hopefully this article helps you as you build your mobile applications. If at all possible, opt for implementing feature flagging instead of manually releasing your apps.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>android</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Implementing &amp; testing Socialite authentication in Laravel</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Tue, 31 Dec 2024 11:52:52 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/implementing-testing-socialite-authentication-in-laravel-35pa</link>
      <guid>https://dev.to/tylerlwsmith/implementing-testing-socialite-authentication-in-laravel-35pa</guid>
      <description>&lt;p&gt;Laravel Socialite is a first-party Laravel package that helps developers implement OAuth &amp;amp; OAuth2 social authentication in their applications. It has built-in support for Facebook, Twitter, Google, LinkedIn, GitHub, GitLab, and Bitbucket. Socialite can support other providers through &lt;a href="https://socialiteproviders.com/" rel="noopener noreferrer"&gt;community packages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explain what Socialite does and does not do.&lt;/li&gt;
&lt;li&gt;Show how to integrate Google authentication into a new Laravel project via Socialite.&lt;/li&gt;
&lt;li&gt;Show an example of testing Socialite.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; you can view the completed project &lt;a href="https://github.com/tylerlwsmith/laravel-socialite-implementation-and-tests" rel="noopener noreferrer"&gt;on my GitHub&lt;/a&gt;. Take a look at it if you would rather just read the completed code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What does Laravel Socialite do and not do?
&lt;/h2&gt;

&lt;p&gt;Socialite is a small package, with its main API primarily consisting of two main methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Socialite::driver($authProvider)-&amp;gt;redirect()&lt;/code&gt; will redirect the user to the specified auth provider, passing any necessary information to the provider via URL parameters.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Socialite::driver($authProvider)-&amp;gt;user()&lt;/code&gt; retrieves user data passed back from the auth provider and makes it available to the endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are additional support methods for settings scopes and optional parameters. You can read about them in the &lt;a href="https://laravel.com/docs/11.x/socialite#main-content" rel="noopener noreferrer"&gt;Socialite documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Socialite does &lt;em&gt;not&lt;/em&gt; do the following,&lt;/strong&gt; leaving the implementation of these items up to the developer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Create database tables or columns needed to store social auth data.&lt;/li&gt;
&lt;li&gt;❌ Create users that don't exist during the authentication process.&lt;/li&gt;
&lt;li&gt;❌ Authenticate the user after a successful OAuth flow.&lt;/li&gt;
&lt;li&gt;❌ Refresh OAuth tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites: creating a Google Cloud project
&lt;/h2&gt;

&lt;p&gt;We will set up a small Socialite project that allows the user to authenticate via Google. To do that, you must create a Google app.&lt;/p&gt;

&lt;p&gt;First &lt;a href="https://console.cloud.google.com/projectcreate" rel="noopener noreferrer"&gt;create a new Google Cloud project&lt;/a&gt;, then configure an &lt;a href="https://console.cloud.google.com/apis/credentials/consent" rel="noopener noreferrer"&gt;OAuth consent screen&lt;/a&gt; for the project. Set the &lt;strong&gt;user type&lt;/strong&gt; to &lt;strong&gt;external&lt;/strong&gt;, then enable the following scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.../auth/userinfo.email&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.../auth/userinfo.profile&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After configuring the consent screen, create an &lt;strong&gt;OAuth 2.0 Client ID&lt;/strong&gt; by visiting the &lt;a href="https://console.cloud.google.com/apis/credentials" rel="noopener noreferrer"&gt;Google Cloud Credentials Page&lt;/a&gt;. Hold on to the &lt;strong&gt;client ID&lt;/strong&gt; and &lt;strong&gt;client secret&lt;/strong&gt;: we will use them later in the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up a minimal Laravel project with Socialite
&lt;/h2&gt;

&lt;p&gt;Create a new Laravel project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;laravel new socialite-tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select the following options from the installer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ┌ Would you like to install a starter kit? ────────────────────┐
 │ No starter kit                                               │
 └──────────────────────────────────────────────────────────────┘

 ┌ Which testing framework do you prefer? ──────────────────────┐
 │ Pest                                                         │
 └──────────────────────────────────────────────────────────────┘

 ┌ Which database will your application use? ───────────────────┐
 │ SQLite                                                       │
 └──────────────────────────────────────────────────────────────┘

 ┌ Would you like to run the default database migrations? ──────┐
 │ Yes                                                          │
 └──────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change into the project directory and install Socialite.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;socialite-tests
composer require laravel/socialite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new migration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:migration add_socialite_fields_to_users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Place the following code in the newly created migration file in &lt;code&gt;database/migrations&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// database/migrations/2024_12_31_075619_add_socialite_fields_to_users.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Migrations\Migration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Schema\Blueprint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Schema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Run the migrations.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google_token'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google_refresh_token'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// If your app allows both password and social logins, you&lt;/span&gt;
            &lt;span class="c1"&gt;// MUST validate that the password is not blank during login.&lt;/span&gt;
            &lt;span class="c1"&gt;// If you do not, an attacker could gain access to an account&lt;/span&gt;
            &lt;span class="c1"&gt;// that uses social login by only knowing the email.&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Reverse the migrations.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dropColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dropColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google_token'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dropColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google_refresh_token'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This migration adds fields that will be provided by Socialite when the user successfully authenticates. In our implementation we're adding these fields directly onto the user table for simplicity. If you wanted to support more providers than Google, you may want to create a separate table that could store multiple providers per user.&lt;/p&gt;

&lt;p&gt;We're setting the password to be nullable because a user will never set a password if they only authenticate via Google. If your app permits social authentication &lt;em&gt;and&lt;/em&gt; password authentication, you &lt;strong&gt;must&lt;/strong&gt; validate that the password is not blank or null when a user attempts to login via a password.&lt;/p&gt;

&lt;p&gt;Run the migration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;config/services.php&lt;/code&gt;, add the following block of code to the end of the services array. You can find a full list of valid Socialite service names in the &lt;a href="https://laravel.com/docs/11.x/socialite#configuration" rel="noopener noreferrer"&gt;configuration docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/services.php&lt;/span&gt;

&lt;span class="s1"&gt;'google'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'client_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_CLIENT_ID'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'client_secret'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_CLIENT_SECRET'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'redirect'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'/auth/google/callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to &lt;code&gt;.env&lt;/code&gt;, using the credentials from your Google app that you created in the "prerequisites" section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env&lt;/span&gt;

&lt;span class="nv"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-google-client-id"&lt;/span&gt;
&lt;span class="nv"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-google-client-secret"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the contents of &lt;code&gt;routes/web.php&lt;/code&gt; with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// routes/web.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Socialite\Facades\Socialite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Socialite\Two\InvalidStateException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Socialite\Two\User&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;OAuth2User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'welcome'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/google/redirect'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/google/callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cd"&gt;/** @var OAuth2User $google_user */&lt;/span&gt;
        &lt;span class="nv"&gt;$google_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InvalidStateException&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;updateOrCreate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$google_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'google_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$google_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$google_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'google_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$google_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'google_refresh_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$google_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/logout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new code in this file implements the routes for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redirecting to Google for social login with the appropriate information.&lt;/li&gt;
&lt;li&gt;Handling the callback from Google. This route creates or updates a user upon login, then authenticates them, and redirects them to the homepage.&lt;/li&gt;
&lt;li&gt;Logging out an authenticated user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, replace the contents of &lt;code&gt;resources/views/welcome.php&lt;/code&gt; with the following markup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- resources/views/welcome.php --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Laravel Socialite Testing Example&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Laravel Socialite Testing Example&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    @if (auth()-&amp;gt;check())
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;User is authenticated.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Name: {{ auth()-&amp;gt;user()-&amp;gt;name }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Email: {{ auth()-&amp;gt;user()-&amp;gt;email }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/auth/logout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    @else
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;User is not authenticated.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/auth/google/redirect"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login with Google&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    @endif
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this completed, we can manually test the app by running the development server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you click the &lt;strong&gt;Login with Google&lt;/strong&gt; link, you should go through the OAuth2 flow and be redirected to the homepage where you can see information about the newly created user from Google.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Socialite with Pest
&lt;/h2&gt;

&lt;p&gt;Our manual tests &lt;em&gt;work&lt;/em&gt;, but we'd like automated tests to verify that we don't accidentally break this functionality in the future.&lt;/p&gt;

&lt;p&gt;We can create a new test file with the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:test AuthRoutesTest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the contents of the newly created &lt;code&gt;tests/Feature/AuthRoutesTest.php&lt;/code&gt; with the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// tests/Feature/AuthRoutesTest.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Testing\RefreshDatabase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Socialite\Contracts\Provider&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;SocialiteProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Socialite\Facades\Socialite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Socialite\Two\User&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;OAuth2User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;Pest\Laravel\assertAuthenticated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;Pest\Laravel\assertGuest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;Pest\Laravel\get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RefreshDatabase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'authentication routes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'redirects login route to correct Google URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/google/redirect'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$redirect_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTargetUrl&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$parsed_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// passed by reference to parse_str()&lt;/span&gt;
        &lt;span class="nb"&gt;parse_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;parse_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$redirect_url&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s1"&gt;'query'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$parsed_query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$redirect_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toStartWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'https://accounts.google.com/o/oauth2/auth'&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$parsed_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toHaveKeys&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'client_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'redirect_uri'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'scope'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'response_type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'state'&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'creates and authenticates a user that does not yet exist'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OAuth2User&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'12345'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tyler Smith'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tyler.smith@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123456789abcdef'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;refreshToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123456789abcdef'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$mock_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mockery&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SocialiteProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$mock_provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'driver'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mock_provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/google/callback'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;assertAuthenticated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'authenticates an existing user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OAuth2User&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'12345'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tyler Smith'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tyler.smith@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123456789abcdef'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;refreshToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123456789abcdef'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$app_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$mock_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mockery&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SocialiteProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$mock_provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'driver'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mock_provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/google/callback'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;assertAuthenticated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'redirects authenticated user to the homepage'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OAuth2User&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'12345'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tyler Smith'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tyler.smith@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123456789abcdef'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;refreshToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123456789abcdef'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$mock_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mockery&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SocialiteProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$mock_provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'driver'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mock_provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/google/callback'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;assertAuthenticated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'returns 400 when the oauth callback url is requested directly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/google/callback'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;assertGuest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How the tests work
&lt;/h2&gt;

&lt;p&gt;When testing the redirect route, we test that Socialite redirects to the correct URL and passes the correct URL parameters.&lt;/p&gt;

&lt;p&gt;When testing the callback routes, we mock Socialite. Mocking isn't my favorite option: in an ideal world, we could swap out Socialite for another OAuth2 implementation and our tests would still work. However, there's not a straight forward way to hook into the authorization grant request that Socialite sends to garnish the access token. Because of this, mocking is the most practical approach to testing Socialite.&lt;/p&gt;

&lt;p&gt;Fluent APIs are tedious to mock via Mockery: you must start from the end call and work your way backwards.&lt;/p&gt;

&lt;p&gt;Here is the Socialite method that our callback endpoint invokes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is how that must be mocked via Mockery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Mock -&amp;gt;user();&lt;/span&gt;
&lt;span class="nv"&gt;$mock_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mockery&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SocialiteProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$mock_provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oauth_user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Mock Socialite::driver('google')&lt;/span&gt;
&lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'driver'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mock_provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we have a test to ensure that navigating directly to the callback URL outside of the OAuth flow returns a 400 status code. We wrapped the call to &lt;code&gt;Socialite::driver('google')-&amp;gt;user()&lt;/code&gt; in the callback endpoint within a &lt;code&gt;try/catch&lt;/code&gt; block. If we hadn't wrapped the Socialite call in a &lt;code&gt;try/catch&lt;/code&gt; block and someone typed the callback URL into their browser, the endpoint would throw an exception with an HTTP &lt;code&gt;500&lt;/code&gt; status code. If your team has monitoring set up for &lt;code&gt;500&lt;/code&gt; status codes, that could cause someone to get paged in the middle of the night.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;This is a minimal integration, and there's a lot more that could be implemented. If we were implementing an integration with a social provider where the user's email could change, this implementation wouldn't work since it matches against the email address. If the user could change their email address within our app, this implementation also wouldn't work for the same reason. However, now that you've seen how to test Socialite, you could write tests for these scenarios and modify the underlying implementation so that they pass.&lt;/p&gt;

&lt;p&gt;I read a lot of blog articles and forum posts about Socialite before I understood how to build my own implementation, how to test it, and what I should think about. I'd like to acknowledge some of those here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stefanzweifel.dev/posts/2016/12/29/how-i-write-integration-tests-for-laravel-socialite-powered-apps/" rel="noopener noreferrer"&gt;How I write integration tests for Laravel Socialite powered apps by Stefan Zweifel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.serversideup.net/t/socialite-best-practices-a-conversation/324" rel="noopener noreferrer"&gt;ServerSideUp forum: Socialite Best Practices, a conversation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/35294257/how-to-test-laravel-socialite" rel="noopener noreferrer"&gt;Stack Overflow: How to Test Laravel Socialite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ux.stackexchange.com/questions/102130/to-link-or-not-to-link-social-logins-with-a-matching-email" rel="noopener noreferrer"&gt;Stack Exchange: To Link or Not to Link Social Logins with a Matching Email&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ux.stackexchange.com/questions/60613/dealing-with-connected-social-accounts-and-potential-orphans" rel="noopener noreferrer"&gt;Stack Exchange: Dealing with Connected Social Accounts and Potential Orphans&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read those if you're interested in digging deeper. Also, let me know if you'd be interested in a part 2 of this post where I dig into handling multiple social providers, handling when a user changes their email address, or handling refresh tokens.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>pest</category>
      <category>socialite</category>
    </item>
    <item>
      <title>12 things I learned about hosting serverless sites on Cloudflare</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Sun, 29 Dec 2024 21:18:54 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/12-things-i-learned-about-hosting-serverless-sites-on-cloudflare-2dml</link>
      <guid>https://dev.to/tylerlwsmith/12-things-i-learned-about-hosting-serverless-sites-on-cloudflare-2dml</guid>
      <description>&lt;p&gt;I spent years avoiding "serverless" architecture. Deploying my applications to a server that I configured is a point of personal pride. And I do that... sometimes. But more often than not, I stop myself from building personal projects because I don't want to manage &lt;em&gt;yet another&lt;/em&gt; server, and I absolutely don't want to pay for hosting. &lt;/p&gt;

&lt;p&gt;Cloudflare's Developer Platform has a generous free-tier for serverless apps, a relational database built on top of SQLite, and a simple path to deployment. I already use Cloudflare for DNS on most of my projects, so I decided I'd give their platform a shot.&lt;/p&gt;

&lt;p&gt;This post will cover 12 things I learned about building and deploying serverless sites using Cloudflare. The post was written in December 2024, so if you're reading this from the distant future some details may have changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Cloudflare has two competing offerings for building serverless websites: Workers and Pages
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Workers&lt;/strong&gt; is a serverless functions platform capable of running code and serving static assets. &lt;strong&gt;Cloudflare Pages&lt;/strong&gt; is a static site platform that is capable of running serverless functions.&lt;/p&gt;

&lt;p&gt;The differences between these two offerings is subtle because they have such similar capabilities. Each has its quirks though, and some of those will be discussed later in this post.&lt;/p&gt;

&lt;p&gt;If you're like me, you'll try both and never be sure if you picked the right one for any given project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;September 2025 update:&lt;/strong&gt; &lt;a href="https://pages.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare's Pages homepage&lt;/a&gt; now has a banner at the top encouraging new users to choose Workers over Pages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. If you want to put your Pages or Workers site on an apex domain, Cloudflare must manage its DNS
&lt;/h2&gt;

&lt;p&gt;If you owned the domain &lt;code&gt;example.com&lt;/code&gt; and you wanted &lt;code&gt;https://example.com&lt;/code&gt; to point to your Cloudflare-hosted website, the DNS for that domain must be managed by Cloudflare.&lt;/p&gt;

&lt;p&gt;You can point to Cloudflare sites on subdomains like &lt;code&gt;www.example.com&lt;/code&gt; or &lt;code&gt;blog.example.com&lt;/code&gt; without Cloudflare managing that domain's DNS. However, Cloudflare must manage a domain's DNS in order to point the &lt;strong&gt;apex domain&lt;/strong&gt; (a domain without a subdomain) to a Workers or Pages site.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. If you know that you're going to deploy a site to Cloudflare, it's worth using Cloudflare's tools to create it
&lt;/h2&gt;

&lt;p&gt;Cloudflare has documentation for hosting various frameworks on both &lt;a href="https://developers.cloudflare.com/workers/frameworks/" rel="noopener noreferrer"&gt;Workers&lt;/a&gt; and &lt;a href="https://developers.cloudflare.com/pages/framework-guides/" rel="noopener noreferrer"&gt;Pages&lt;/a&gt;. Each guide has two sets of instructions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to set up a new project in your framework of choice to run on Cloudflare by using &lt;code&gt;npm create cloudflare ...&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;How to retrofit an existing project in your framework of choice to run on Cloudflare.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Having tried both, I'd recommend using &lt;code&gt;npm cloudflare create&lt;/code&gt; if you know that you're going to deploy your site to Cloudflare. It gives you everything you need out of the box while leaving most of the framework scaffolding untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. When you scaffold a site using &lt;code&gt;npm cloudflare create&lt;/code&gt;, you can run local versions of Cloudflare services
&lt;/h2&gt;

&lt;p&gt;When I began building an Astro site that needed Cloudflare D1 to store form submissions, I wasn't yet sure if I could use D1 locally. &lt;/p&gt;

&lt;p&gt;It turns out that when I scaffolded Astro using &lt;code&gt;npm create cloudflare&lt;/code&gt;, some kind of local emulation for Cloudflare's services was installed into my site. This enables the site to run Cloudflare's D1, R2, and KV locally. However, these services must be added to &lt;code&gt;wrangler.toml&lt;/code&gt; before they can be used.&lt;/p&gt;

&lt;p&gt;Since I've only deployed Astro to Cloudflare, I'm not 100% certain that every framework works this way. That said, I'd &lt;em&gt;guess&lt;/em&gt; that any framework with a Cloudflare adapter installed gets access to these tools regardless of how it was created.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. To use a Cloudflare service locally, that service must have an ID in &lt;code&gt;wrangler.toml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Before I was certain that I wanted to use Cloudflare's Developer Platform, I wanted to set up a local D1 instance for experimentation. I added the following to my &lt;code&gt;wrangler.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[d1_databases]]&lt;/span&gt;
&lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MY_DB"&lt;/span&gt;
&lt;span class="py"&gt;database_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-database"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I started my site with &lt;code&gt;npm run dev&lt;/code&gt;, it crashed with the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Processing wrangler.toml configuration:
  - "d1_databases[0]" bindings must have a "database_id" field but got {"binding":"MY_DB","database_name":"my-database"}.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out that databases must have a &lt;code&gt;database_id&lt;/code&gt; in &lt;code&gt;wrangler.toml&lt;/code&gt; in order to run locally. However, the ID doesn't necessarily need to be a valid ID of a D1 database that exists on Cloudflare. You can set &lt;code&gt;database_id&lt;/code&gt; to any non-blank string and the site will boot without issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[d1_databases]]&lt;/span&gt;
&lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MY_DB"&lt;/span&gt;
&lt;span class="py"&gt;database_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-database"&lt;/span&gt;
&lt;span class="py"&gt;database_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you can create your first migration with the following command, replacing &lt;code&gt;&amp;lt;DATABASE_NAME&amp;gt;&lt;/code&gt; with the name of the database defined in &lt;code&gt;wrangler.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler d1 migrations create &amp;lt;DATABASE_NAME&amp;gt; &amp;lt;MIGRATION_NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you've filled in your generated migration file with the SQL statements that you want executed, you can run the migration on your local database with the command below. &lt;strong&gt;You must include the &lt;code&gt;--local&lt;/code&gt; flag to ensure it runs against the local database instead.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler d1 migrations apply &amp;lt;DATABASE_NAME&amp;gt; &lt;span class="nt"&gt;--local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once created, you can find the SQLite database file from the main project directory at &lt;code&gt;.wrangler/state/v3/d1/&amp;lt;FILENAME&amp;gt;.sqlite&lt;/code&gt;. The filename will be long and machine generated, but it will end with the &lt;code&gt;.sqlite&lt;/code&gt; extension. You can use a client like TablePlus to connect to it.&lt;/p&gt;

&lt;p&gt;When it comes time to deploy your site, you'll want to create a real D1 database on Cloudflare and replace &lt;code&gt;database_id&lt;/code&gt; in &lt;code&gt;wrangler.toml&lt;/code&gt; with the ID of the real production database. You can create a database through Cloudflare's UI, or by running the following wrangler command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler d1 create &amp;lt;DATABASE_NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the command runs, it will give you toml to copy and paste into your &lt;code&gt;wrangler.toml&lt;/code&gt; file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; If you know that you're going to deploy a D1 database to Cloudflare, it's worth creating the production database at the start of a project and putting its info into &lt;code&gt;wrangler.toml&lt;/code&gt;. Cloudflare D1 is charged by &lt;em&gt;rows written&lt;/em&gt;, &lt;em&gt;rows read&lt;/em&gt;, and &lt;em&gt;storage&lt;/em&gt; instead of a monthly fee. The free tier is generous, and creating an empty database costs nothing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  6. Compatibility with Node.js isn't 100%
&lt;/h2&gt;

&lt;p&gt;Cloudflare Workers and Pages don't provide the Node runtime APIs by default. You can enable &lt;a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/#built-in-nodejs-runtime-apis" rel="noopener noreferrer"&gt;&lt;em&gt;some&lt;/em&gt; of Node's runtime APIs&lt;/a&gt; by setting the following top-level configuration option in &lt;code&gt;wrangler.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;compatibility_flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"nodejs_compat"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, this only enables a small subset of Node's runtime APIs. I wasn't able to use &lt;a href="https://www.nodemailer.com/" rel="noopener noreferrer"&gt;Nodemailer&lt;/a&gt; in one of my projects because it relied on Node features that aren't available in Cloudflare workers. This isn't just limited to Nodemailer: there is no way to do SMTP mailing with &lt;em&gt;any&lt;/em&gt; library from a Cloudflare Worker: the protocol requires Node features that Workers don't implement–even when Node compatibility is enabled. I had to rewrite my email code to use Mailgun's API directly.&lt;/p&gt;

&lt;p&gt;Hosting on Cloudflare means that you will occasionally run into compatibility issues. This is the cost of free hosting.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Cloudflare Pages do not allow you to set production environment variables in the Cloudflare UI
&lt;/h2&gt;

&lt;p&gt;When deploying to Cloudflare pages, you must set any environment variables that will be used by the site in &lt;code&gt;wrangler.toml&lt;/code&gt;. You may set &lt;em&gt;secrets&lt;/em&gt; in the Cloudflare UI, but secrets cannot be retrieved in the UI after they are written: only replaced.&lt;/p&gt;

&lt;p&gt;This limitation can be frustrating if your project has data that's not &lt;em&gt;quite&lt;/em&gt; a secret but you'd rather keep out of a public repository. For example, a site I built notifies a list of email addresses when a user submits a form. The recipients email addresses aren't &lt;em&gt;quite&lt;/em&gt; a secret, but I don't want to commit them to version control. I'd also like to be able to easily check what email addresses currently receive notifications. &lt;/p&gt;

&lt;p&gt;If I deploy this site to Pages, I'll have to set the email recipient list in one of the following ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Put the recipient email addresses in my public repo where anyone could see them.&lt;/strong&gt; While this is the simplest approach, the notification recipients might prefer to keep their email addresses private.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set the recipient email addresses as a secret in the Cloudflare UI.&lt;/strong&gt; If I did this, I would not be able to check what emails are currently in the recipient list. Secrets cannot be retrieved via the Cloudflare UI after they are written: only replaced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store recipient  email addresses in D1 or KV.&lt;/strong&gt; This feels like a lot of work for something that should be simple, but it might be the best option.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the time of writing, Cloudflare Workers do not have this limitation. In Workers, you can set environment variables from within Cloudflare's UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Worker environment variables set in Cloudflare's UI will be overwritten by variables in &lt;code&gt;wrangler.toml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;With Workers, you can set environment variables in the Cloudflare UI. However, the default behavior is to overwrite them with variables set in &lt;code&gt;wrangler.toml&lt;/code&gt; on each deployment.&lt;/p&gt;

&lt;p&gt;To prevent &lt;code&gt;wrangler.toml&lt;/code&gt; from overwriting your variables on deploy, set the following top-level configuration option in &lt;code&gt;wrangler.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;keep_vars&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wouldn't be surprised if &lt;code&gt;keep_vars&lt;/code&gt; disappears at some point in the future. Cloudflare appears to be moving towards configuration-as-code, and having environment variables stored outside of Git is at odds with this approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Environment variables aren't &lt;em&gt;actually&lt;/em&gt; environment variables
&lt;/h2&gt;

&lt;p&gt;In Cloudflare Workers, environment variables that are set via &lt;code&gt;[vars]&lt;/code&gt; in &lt;code&gt;wrangler.toml&lt;/code&gt; or in the Cloudflare UI aren't accessible via &lt;code&gt;process.env&lt;/code&gt; or &lt;code&gt;import.meta.env&lt;/code&gt;: they are provided to the site as a part of Cloudflare's top-level handler function.&lt;/p&gt;

&lt;p&gt;How your framework makes these variables available may differ. In Astro, you can access these variables via &lt;code&gt;context.locals.runtime.env&lt;/code&gt;. Other frameworks may provide other options.&lt;/p&gt;

&lt;p&gt;However, it's important to note that &lt;code&gt;wrangler.toml&lt;/code&gt; should not be used to store secrets. Locally, secrets should be added to a Git ignored &lt;code&gt;.dev.vars&lt;/code&gt; file in the main project directory. In production, secrets are added via the Cloudflare UI.&lt;/p&gt;

&lt;p&gt;You can also use &lt;code&gt;.dev.vars&lt;/code&gt; to override variables set in &lt;code&gt;wrangler.toml&lt;/code&gt; when developing locally. Changes made to &lt;code&gt;.dev.vars&lt;/code&gt; may require restarting the server locally to resolve the correct value.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Pages deployments create publicly accessible "preview deployments" that stick around for awhile
&lt;/h2&gt;

&lt;p&gt;When you push changes to a Pages site, it creates a "preview" URL on a subdomain of &lt;code&gt;pages.dev&lt;/code&gt;. Anyone with the URL can view that version of the site, and they are not deleted automatically when a new version of the site is released.&lt;/p&gt;

&lt;p&gt;If that makes you nervous, Cloudflare has &lt;a href="https://developers.cloudflare.com/pages/configuration/preview-deployments/#customize-preview-deployments-access" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; that lays out how to restrict access to preview deployments through the Cloudflare UI. You can also manually delete previous preview deployments through the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Previous deployments of workers are URL accessible
&lt;/h2&gt;

&lt;p&gt;Similar to Pages' "preview deployments," previous worker deployments are URL accessible via a subdomain of &lt;code&gt;workers.dev&lt;/code&gt;. Anyone with the URL can view that deployed version of the site.&lt;/p&gt;

&lt;p&gt;If that makes you nervous, you can disable them by adding the following top-level configuration option in &lt;code&gt;wrangler.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;preview_urls&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no way to delete previous Worker deployments within the Cloudflare UI, but I believe that they are automatically removed after some duration of time (though I'm not 100% sure).&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Initial deployments are SLOW
&lt;/h2&gt;

&lt;p&gt;After running &lt;code&gt;npx wrangler depoy&lt;/code&gt; for the first time, Cloudflare will create a Worker or Page and provide you a preview URL immediately. If you visit that URL right away, you'll see a browser error.&lt;/p&gt;

&lt;p&gt;It takes Cloudflare several minutes to set up a new project. Come back to it in a few minutes and you should have a working website.&lt;/p&gt;




&lt;p&gt;I hope that you've found this post helpful as you explore hosting your own projects on Cloudflare. It has its quirks, but I've had a good experience so far.&lt;/p&gt;

&lt;p&gt;The platform still seems to be evolving rapidly, so please comment to let me know if any of the information in the post has changed and I'll get it updated.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>First impressions of Astro: what I liked and disliked</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Sat, 28 Dec 2024 09:00:22 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/first-impressions-of-astro-what-i-liked-and-disliked-22nj</link>
      <guid>https://dev.to/tylerlwsmith/first-impressions-of-astro-what-i-liked-and-disliked-22nj</guid>
      <description>&lt;p&gt;I recently started using Astro to rebuild side projects that were originally built with WordPress, Go, Rails, and Hugo. I picked Astro because it had a React-like DX with good language server support, and because it was compatible with serverless hosting platforms that had a generous free tier (Cloudflare, AWS Lambda, etc). &lt;/p&gt;

&lt;p&gt;I didn't know much about Astro when I started using it. Now that I've migrated multiple sites, I wanted to share what I liked and disliked about the framework for anyone else considering using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Astro: what I liked
&lt;/h2&gt;

&lt;p&gt;At its core, Astro is a static site generator with the ability to produce dynamic server-rendered pages when needed. Astro is a great fit for blogs or small marketing sites with limited interactivity. The framework provides most of the DX from Next.js without the React.js overhead. &lt;/p&gt;

&lt;h3&gt;
  
  
  Good IntelliSense and code formatting in a server rendered template language
&lt;/h3&gt;

&lt;p&gt;Let's be honest: good language server support and code formatting are severely lacking in traditional server-rendered templating languages. Go templates, Jinja, ERB, and EJS are painfully behind with tooling when compared to their React/Vue/Svelte counterparts. Most server-rendered templating languages have no way of knowing what variables are in scope or what their types are. &lt;/p&gt;

&lt;p&gt;With Astro, all of these features are one VS Code extension away.&lt;/p&gt;

&lt;p&gt;Inside of Astro templates, you set your data at the top of the template inside of a "code fence" that either runs at build time or when responding to a request on the server. Here's what that looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../layouts/Layout.astro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getPosts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../data/posts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;}[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPosts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt; &lt;span class="na"&gt;pageTitle&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Posts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Posts&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/posts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because all of the data for the template is loaded in the "code fence" above the template, the language server can provide auto-completion for the properties of any object defined within the scope. It will also indicate when you are trying to use a variable that doesn't exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Astro files can be "components"
&lt;/h3&gt;

&lt;p&gt;One of my biggest gripes with traditional templating languages like Go templates, Jinja, and EJS is that they don't have "components" that can accept children. Most of my websites have a constrained-width "container" UI element of some kind, which ensures that content doesn't fly out to the end of the screen on ultra-wide monitors. If you have a &lt;code&gt;.container&lt;/code&gt; class that you manually add to &lt;code&gt;&amp;lt;div /&amp;gt;&lt;/code&gt; elements, then this usually works fine. However, if you're using a utility CSS framework like Tailwind then you may find yourself adding the following code to every single page template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mx-auto px-4 sm:max-w-md md:max-w-lg lg:max-w-xl xl:max-w-2xl"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Inner-page content goes here... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you eventually need to change these classes, it's an error-prone pain to update each file manually. But if your templating language doesn't have components that can accept children, it's almost inevitable. &lt;/p&gt;

&lt;p&gt;Unlike those traditional templating languages, Astro templates can be used as components that accept children using a &lt;code&gt;&amp;lt;slot /&amp;gt;&lt;/code&gt; tag. A long string of utility classes could be extracted into a reusable Astro component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
  &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mx-auto px-4 sm:max-w-md md:max-w-lg lg:max-w-xl xl:max-w-2xl"&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;slot&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Astro component could then be consumed from another Astro file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/Container.astro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello, world!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro files aren't limited to a single slot: they can have multiple.&lt;/p&gt;

&lt;p&gt;My favorite feature of Astro components is that they can accept props within the code fence. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pageTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;pageDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pageTitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageDescription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;doctype&lt;/span&gt; &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pageTitle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pageDescription&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;slot&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component can then accept props when used within another file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../layouts/Layout.astro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;
  &lt;span class="na"&gt;pageTitle&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Tyler's Blog"&lt;/span&gt;
  &lt;span class="na"&gt;pageDescription&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"I don't really post on here."&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Tyler's blog&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sorry, there's no content yet...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A frontend asset pipeline with live reloads is built-in
&lt;/h3&gt;

&lt;p&gt;I've &lt;a href="https://dev.to/tylerlwsmith/build-a-vite-5-backend-integration-with-flask-jch"&gt;built my own server-side integrations with Vite&lt;/a&gt; before. If you're trying to get something online quickly, this is the kind of commodity feature that you want to avoid building yourself. With Astro, it's built-in.&lt;/p&gt;

&lt;p&gt;If you want to add a custom script to an Astro page or component, all you have to do is drop a script tag on the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;This is my page&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"../assets/script.ts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro will automatically compile TS and JS files as a part of a site's build process. You can also write scripts that use imports from &lt;code&gt;node_modules&lt;/code&gt; inside of a script tag within an Astro component, and Astro will compile that during build time and extract it to its own file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// This will also compile down to a JavaScript file at build time.&lt;/span&gt;

  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AxiosRequestConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AxiosResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AxiosRequestConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AxiosResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/some-endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can include CSS or Scss styles in an Astro file by importing them within the code fence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../assets/styles.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro also provides the ability to do &lt;strong&gt;scoped styles&lt;/strong&gt; by using a style tag in an Astro file. This feature may be familiar to Vue users.&lt;/p&gt;

&lt;p&gt;Given the following Astro file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  .heading &lt;span class="si"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"heading"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Hello, world!
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro will render the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  .heading[data-astro-cid-6d7mwtum] &lt;span class="si"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"heading"&lt;/span&gt; &lt;span class="na"&gt;data-astro-cid-6d7mwtum&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Hello, world!
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The injected &lt;code&gt;data-astro&lt;/code&gt; attributes scope the styles to the current component, ensuring that they don't override styles in other components that may use the same class name.&lt;/p&gt;

&lt;h3&gt;
  
  
  First-class markdown &amp;amp; MDX support
&lt;/h3&gt;

&lt;p&gt;I've lost days trying to figure out &lt;a href="https://dev.to/tylerlwsmith/quick-comparison-of-mdx-integration-strategies-with-next-js-1kcm"&gt;the different ways to incorporate markdown and MDX into Next.js&lt;/a&gt;. With Astro, it's built-in. Here's what it looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../content/blog-post-1.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  Actions
&lt;/h3&gt;

&lt;p&gt;Actions provide a type-safe way of running backend functions. They provide validation and can handle both JSON and form data. They're easily one of Astro's killer features: all of this would need to be hand-wired in a Next.js app in a bespoke way. They require a little more code than can fit neatly into an example, but they're pretty elegant to use. I'd recommend reading the &lt;a href="https://docs.astro.build/en/guides/actions/" rel="noopener noreferrer"&gt;actions docs page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  No runtime JS cost
&lt;/h3&gt;

&lt;p&gt;There are an endless number of Twitter devs that say React is "fast enough." For a lot of things it's not.&lt;/p&gt;

&lt;p&gt;I use Rasbperry Pi 4s for little projects, and you can &lt;em&gt;feel&lt;/em&gt; the runtime cost of React. I'm sure it's the same for inexpensive Android phones, except in that case the JS overhead will also drain the battery.&lt;/p&gt;

&lt;p&gt;If the only interactivity that my site needs is toggling a nav menu, I'd rather wire that up myself. I'll happily reach for React when I need it, but for so many projects I don't actually need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Astro: what I disliked
&lt;/h2&gt;

&lt;p&gt;The things that I dislike about Astro are not unique to the framework: they are ideas borrowed from other tools in the JavaScript ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  File-based routing
&lt;/h3&gt;

&lt;p&gt;Because Astro employs file-based routing, half of the files in an Astro project end up named &lt;code&gt;index.(astro|js|ts)&lt;/code&gt; or &lt;code&gt;[id].(astro|js|ts)&lt;/code&gt;. File-based routing is an obnoxious pattern that swept the frontend world by storm after Next.js implemented it, and it comes with many drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You'll often have 5 tabs open in your editor with the same filename. It will take a minimum of 3 guesses to find the tab you're looking for.&lt;/li&gt;
&lt;li&gt;The editor's file fuzzy finder is far less useful because so many files have the same name.&lt;/li&gt;
&lt;li&gt;File-based routing scatters one of an application's core concerns across many files and folders, making it difficult to see all routes at a glance within the editor. Instead, you must drill down into several folders to understand what pages are available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll admit: file-based routing feels great when you're making a site with under 10 pages. But as a site grows it adds friction, and you fight the feature more than you benefit from it.&lt;/p&gt;

&lt;p&gt;In the JavaScript ecosystem, Remix stands apart by offering a version of file-based routing that &lt;a href="https://remix.run/docs/fr/main/discussion/routes#conventional-route-configuration" rel="noopener noreferrer"&gt;flattens all routes into a single directory&lt;/a&gt;, and allows a user to opt out of file-based routing entirely with &lt;a href="https://remix.run/docs/fr/main/discussion/routes#manual-route-configuration" rel="noopener noreferrer"&gt;manual route configuration&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;File-based routing is my biggest complaint about Astro, but it's a difficult feature to escape. It is implemented in Next.js, Nuxt.js, SvelteKit, and others. What's even stranger is that these frameworks are largely unopinionated about the filenames for other parts of the application. In start contrast to Ruby on Rails, most JavaScript frameworks give you a great degree of freedom in file names and project structure–&lt;em&gt;except&lt;/em&gt; for routing. It's a special case, and special cases add complexity to software.&lt;/p&gt;

&lt;h3&gt;
  
  
  One component per file (kind of)
&lt;/h3&gt;

&lt;p&gt;A JavaScript language feature that I really like is the ability to define multiple variables, functions, and classes in a single file. This makes it easy to colocate related functionality without having to prematurely extract it to other files because of language-level constraints.&lt;/p&gt;

&lt;p&gt;Much like Vue's single-file components, Astro files allow defining one component per file. This feels tedious to me, but Astro provides a workaround.&lt;/p&gt;

&lt;p&gt;Astro can embed pre-rendered React, Vue, Svelte, Solid, and Preact components directly into its templates without loading any client-side JavaScript. Preact components pair &lt;em&gt;reasonably&lt;/em&gt; well with Astro because Preact JSX is much closer to HTML than React JSX. It does become awkward managing both Astro and Preact components in the same project though, and once I begin using Preact components I find myself moving most of the UI out of Astro files and into Preact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts on Astro
&lt;/h2&gt;

&lt;p&gt;If you're an avid user of Next.js, Nuxt, or SvelteKit and you are happy with your framework, you might not get much out of Astro. However, if you want to ship less JavaScript bloat to your users while retaining the DX of something like Next.js, then Astro might be for you.&lt;/p&gt;

&lt;p&gt;Astro is geared towards content-driven websites, and provides markdown support out-of-the-box. Because of its focus on content, it is an ideal developer blogging platform to replace a WordPress or Hugo site. However, it's capable of much more than just content sites through features like Actions. &lt;/p&gt;

&lt;p&gt;Despite my strong distaste for file-based routing, my biggest concern with adopting Astro is the potential for breaking changes that would force me to rebuild my sites. JavaScript tools are much more aggressive with breaking changes than tools you find in other language ecosystems. Because I'm so new to Astro, I don't know how much changes from one major version to the next. Even with this concern, I plan to move 5-to-6 of my sites from other platforms to Astro so I can take advantage of its top-notch DX and host the sites inexpensively.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Upsert a row in a DB that doesn't use primary keys or unique constraints</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Sat, 19 Oct 2024 01:50:34 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/upsert-a-row-in-a-db-that-doesnt-use-primary-keys-or-unique-constraints-2jnl</link>
      <guid>https://dev.to/tylerlwsmith/upsert-a-row-in-a-db-that-doesnt-use-primary-keys-or-unique-constraints-2jnl</guid>
      <description>&lt;p&gt;During my 7-year career as a programmer, I've interacted with SQL via an ORM most of the time. One feature from Laravel's Eloquent ORM that I find particularly useful is its &lt;code&gt;updateOrInsert()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateOrInsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'slug'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'about'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// matching condition&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Like and subscribe'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// created or updated values&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, Eloquent will look for a row in the &lt;code&gt;posts&lt;/code&gt; table where the &lt;code&gt;slug&lt;/code&gt; equals &lt;code&gt;"about"&lt;/code&gt;. If a row exists with that slug, Eloquent will update that row's content to &lt;code&gt;"Like and subscribe"&lt;/code&gt;. If a row &lt;em&gt;doesn't&lt;/em&gt; exist with that slug, Eloquent will create a new row with the slug of &lt;code&gt;"about"&lt;/code&gt; and the content of &lt;code&gt;"Like and subscribe"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: no primary key or unique constraints
&lt;/h2&gt;

&lt;p&gt;I'm currently writing a migration script to move page data from an old WordPress site to a new WordPress site. The script connects to the database of the old site then creates an SQL file that can be imported into the database of the new site. The new site already has some pages that have been moved manually, but their content may be out-of-date. When a page already exists on the new site, we don't want to create it again: we want to update the page that's already there. We can determine if the page already exists by using the &lt;code&gt;post_name&lt;/code&gt; column in the WordPress database, which corresponds to the page's URL slug.&lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;post_name&lt;/code&gt; were the primary key, we could accomplish something similar to Eloquent's &lt;code&gt;createOrUpdate()&lt;/code&gt; method using a &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/replace.html" rel="noopener noreferrer"&gt;&lt;code&gt;REPLACE&lt;/code&gt; statement&lt;/a&gt;, but the &lt;code&gt;post_name&lt;/code&gt; is not the primary key.&lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;post_name&lt;/code&gt; had a unique constraint, we could accomplish something similar to Eloquent's &lt;code&gt;createOrUpdate()&lt;/code&gt; method using an &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/replace.html" rel="noopener noreferrer"&gt;&lt;code&gt;INSERT ... ON DUPLICATE KEY UPDATE&lt;/code&gt; statement&lt;/a&gt;, but the &lt;code&gt;post_name&lt;/code&gt; does not have a unique constraint.&lt;/p&gt;

&lt;p&gt;Ah, the joys of WordPress.&lt;/p&gt;

&lt;p&gt;I looked into MySQL's &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/if.html" rel="noopener noreferrer"&gt;&lt;code&gt;IF&lt;/code&gt; statement&lt;/a&gt;, but the &lt;code&gt;IF&lt;/code&gt; statement only works in stored procedures, triggers, and functions. I did not want to create stored procedures in the SQL file that I would import to the new site's database, so I had to search for other options.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: 2 statements
&lt;/h2&gt;

&lt;p&gt;The simplest solution I could find to emulating Eloquent's &lt;code&gt;updateOrInsert()&lt;/code&gt;'s functionality in pure SQL was to break the problem into two pieces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update any existing rows with a matching slug.&lt;/li&gt;
&lt;li&gt;Create a new row if there are no rows with matching slugs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's what that looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Update the post if it already exists.&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;wp_posts&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;post_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'page'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;post_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'About'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;post_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Like and subscribe'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;post_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'about'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create a new post if it does not exist.&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;wp_posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'about'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'page'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'About'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Like and subscribe'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_posts&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;post_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'about'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This example omits many of WordPress's required &lt;code&gt;wp_posts&lt;/code&gt; columns for brevity and clarity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Learning about the &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/insert-select.html" rel="noopener noreferrer"&gt;&lt;code&gt;INSERT ... SELECT&lt;/code&gt; statement&lt;/a&gt; was the "aha" moment for me. It is intended to use the results of a query from another table to perform many inserts at once. However, you can use-and-abuse SQL's capabilities by providing your own values and only performing the insert when a post with a &lt;code&gt;post_name&lt;/code&gt; of "about" does not exist.&lt;/p&gt;

&lt;p&gt;Is this the most elegant and efficient solution to this problem? Probably not. But it's good enough for a one-off migration of a WordPress site, and it allows you to update or insert a row without needing stored procedures or any application logic. Hopefully this post helps you as you write powerful queries without the aid of an ORM.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>sql</category>
    </item>
    <item>
      <title>Stop the cursor from jumping to the corner of the screen on a Lenovo Yoga laptop</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Wed, 16 Oct 2024 00:23:37 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/stop-the-cursor-from-jumping-to-the-corner-of-the-screen-on-a-lenovo-yoga-laptop-2mj4</link>
      <guid>https://dev.to/tylerlwsmith/stop-the-cursor-from-jumping-to-the-corner-of-the-screen-on-a-lenovo-yoga-laptop-2mj4</guid>
      <description>&lt;p&gt;I was using Windows 11 on my Lenovo Yoga today, and suddenly my cursor started jumping to the top left corner of my screen. This was happening every few seconds, and it even happened when I wasn't touching the trackpad or screen.&lt;/p&gt;

&lt;p&gt;I did the following troubleshooting steps to try to resolve the issue, all of which were unsuccessful:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I restarted the computer.&lt;/li&gt;
&lt;li&gt;I cleaned the sides of the trackpad with a toothpick in case something was stuck.&lt;/li&gt;
&lt;li&gt;I adjusted the mouse pointer speed in the mouse settings.&lt;/li&gt;
&lt;li&gt;I performed a &lt;a href="https://support.microsoft.com/en-us/topic/how-to-perform-a-clean-boot-in-windows-da2f9573-6eec-00ad-2f8a-a97a1807f3dd" rel="noopener noreferrer"&gt;clean boot&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I updated the drivers and BIOS by using &lt;a href="https://support.lenovo.com/us/en/downloads/ds012808-lenovo-system-update-for-windows-10-7-32-bit-64-bit-desktop-notebook-workstation" rel="noopener noreferrer"&gt;Lenovo System Update&lt;/a&gt; several times.&lt;/li&gt;
&lt;li&gt;I disabled the trackpad in Windows Settings.&lt;/li&gt;
&lt;li&gt;I disabled the touchscreen using Device Manager and plugged in a mouse.&lt;/li&gt;
&lt;li&gt;I disabled every mouse listed in Device Manager.&lt;/li&gt;
&lt;li&gt;I attempted to update the drivers for the mouse, touchscreen, and trackpad.&lt;/li&gt;
&lt;li&gt;I turned off Bluetooth incase a wireless mouse was somehow connected.&lt;/li&gt;
&lt;li&gt;I hit my computer hard many times.&lt;/li&gt;
&lt;li&gt;I created a Pop!_OS bootable USB and booted into it to ensure that it wasn't a Windows setting that was causing this.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even when I booted into Linux, I was still experiencing my cursor jumping around the screen to the top left corner. Curiously however, the cursor didn't jump around when I was in the system BIOS. This meant that it probably wasn't strictly a hardware issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  The culprit
&lt;/h2&gt;

&lt;p&gt;The culprit was not the touchpad, touchscreen, or mouse: &lt;strong&gt;it was the "Pen."&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I found this curious because my Lenovo Yoga did not come with a Pen, nor have I ever used one with this laptop. But disabling the pen fixes the issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disabling the pen on Windows
&lt;/h2&gt;

&lt;p&gt;To disable the pen on Windows, open the start menu, type "Device Manager," then select &lt;strong&gt;Device Manager&lt;/strong&gt; to open it. Inside Device Manager, open &lt;strong&gt;Human Interface Devices&lt;/strong&gt;, right-click &lt;strong&gt;HID-compliant pen&lt;/strong&gt;, then select &lt;strong&gt;Disable device&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It may take a few attempts to do this successfully while the cursor is jumping around, but once you disable the pen it should stop the cursor jumping immediately. If you disable the pen and the cursor is still jumping around, then you unfortunately have a different problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disabling the pen on Linux
&lt;/h2&gt;

&lt;p&gt;To disable the pen on Linux, you'll need a package called &lt;code&gt;xinput&lt;/code&gt;. I'll let you look up the instructions for installing that on your respective distro.&lt;/p&gt;

&lt;p&gt;Once you've ensured that &lt;code&gt;xinput&lt;/code&gt; is installed, run the following command to list the devices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xinput list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;xinput&lt;/code&gt; should display a list like the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⎡ Virtual core pointer                        id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ SYNACFFE:00 06CB:CEFE Mouse               id=9    [slave  pointer  (2)]
⎜   ↳ SYNACFFE:00 06CB:CEFE Touchpad            id=10   [slave  pointer  (2)]
⎜   ↳ Wacom HID 5362 Pen stylus                 id=11   [slave  pointer  (2)]
⎜   ↳ Wacom HID 5362 Pen eraser                 id=16   [slave  pointer  (2)]
⎜   ↳ Wacom HID 5362 Finger touch               id=12   [slave  pointer  (2)]
⎜   ↳ Logitech USB Optical Mouse                id=8    [slave  pointer  (2)]
⎣ Virtual core keyboard                       id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard                 id=5    [slave  keyboard (3)]
    ↳ Video Bus                                   id=6    [slave  keyboard (3)]
    ↳ Power Button                                id=7    [slave  keyboard (3)]
    ↳ Ideapad extra buttons                       id=13   [slave  keyboard (3)]
    ↳ sof-hda-dsp Headphone                       id=14   [slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard                id=15   [slave  keyboard (3)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for the entry named &lt;code&gt;Wacom HID 5362 Pen stylus&lt;/code&gt; and grab its ID. You can disable the device by it's ID with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# "11" is the device ID for the pen that we found in the step above.&lt;/span&gt;

xinput disable 11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run &lt;code&gt;xinput list&lt;/code&gt; again to confirm that the pen device has been disabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xinput list

⎡ Virtual core pointer                        id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ SYNACFFE:00 06CB:CEFE Mouse               id=9    [slave  pointer  (2)]
⎜   ↳ SYNACFFE:00 06CB:CEFE Touchpad            id=10   [slave  pointer  (2)]
⎜   ↳ Wacom HID 5362 Pen eraser                 id=16   [slave  pointer  (2)]
⎜   ↳ Logitech USB Optical Mouse                id=8    [slave  pointer  (2)]
⎜   ↳ Wacom HID 5362 Finger touch               id=12   [slave  pointer  (2)]
⎣ Virtual core keyboard                       id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard                 id=5    [slave  keyboard (3)]
    ↳ Video Bus                                   id=6    [slave  keyboard (3)]
    ↳ Power Button                                id=7    [slave  keyboard (3)]
    ↳ Ideapad extra buttons                       id=13   [slave  keyboard (3)]
    ↳ sof-hda-dsp Headphone                       id=14   [slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard                id=15   [slave  keyboard (3)]
∼ Wacom HID 5362 Pen stylus  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you disable the pen, it should stop the cursor jumping immediately. If you disable the pen and the cursor is still jumping around, then you unfortunately have a different problem.&lt;/p&gt;




&lt;p&gt;I hope you found this helpful. I'm frustrated that I'm experiencing this issue: in the year and a half I've owned this machine I've probably only used it less than 100 hours. If I had purchased the machine for digital art I'd be furious. Thankfully I'm terrible at art, so I can continue using this machine without missing the pen functionality.&lt;/p&gt;

</description>
      <category>troubleshooting</category>
      <category>linux</category>
      <category>windows</category>
    </item>
    <item>
      <title>The winner of WordPress vs WP Engine will be Microsoft</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Tue, 15 Oct 2024 10:14:53 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/the-winner-of-wordpress-vs-wp-engine-will-be-microsoft-427b</link>
      <guid>https://dev.to/tylerlwsmith/the-winner-of-wordpress-vs-wp-engine-will-be-microsoft-427b</guid>
      <description>&lt;p&gt;Automattic, the commercial company that guides WordPress, has launched a scorched-Earth campaign against WP Engine. For those who don't know, WP Engine is a popular hosting provider for WordPress websites. It also owns many of the plugins that elevate WordPress from a simple blogging platform to an enterprise-capable content management system.&lt;/p&gt;

&lt;p&gt;Less than a month ago, Automattic CEO Matt Muellenweg delivered a keynote at WordCamp US that condemned WP Engine for its lack of contributions to WordPress core. Mullenweg went on to make the argument that private equity firms like the one that owns WP Engine hollow out and destroy open source communities.&lt;/p&gt;

&lt;h2&gt;
  
  
  An abbreviated timeline
&lt;/h2&gt;

&lt;p&gt;The weeks that have followed the WordCamp keynote have felt like months. In that time, the following events have unfolded:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 23:&lt;/strong&gt; WP Engine sent a &lt;a href="https://wpengine.com/wp-content/uploads/2024/09/Cease-and-Desist-Letter-to-Automattic-and-Request-to-Preserve-Documents-Sent.pdf" rel="noopener noreferrer"&gt;cease and desist letter&lt;/a&gt; to Automattic. The letter includes screenshots of texts from Mullenweg threatening to take a "scorched earth nuclear approach" with WP Engine if they did not agree to pay a percentage of their gross revenue to Automattic for a WordPress trademark license (this is now known to be &lt;a href="https://automattic.com/wp-content/uploads/2024/09/term-sheet-wp-engine-inc.-automattic-trademark-license_09.19.2024-1.pdf" rel="noopener noreferrer"&gt;8%&lt;/a&gt;). WP Engine has existed since 2010 and has never previously needed a license.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 24:&lt;/strong&gt; While the WordPress Foundation's trademark policy had permitted the free use of "WP" as recently as &lt;a href="https://web.archive.org/web/20240912061820/https://wordpressfoundation.org/trademark-policy/" rel="noopener noreferrer"&gt;mid-September&lt;/a&gt;, the policy was &lt;a href="https://web.archive.org/web/20240924175241/https://wordpressfoundation.org/trademark-policy/" rel="noopener noreferrer"&gt;updated&lt;/a&gt; to call out WP Engine's usage of "WP" because it "confuses people."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 25:&lt;/strong&gt; In retaliation for the cease and desist letter, &lt;a href="https://wordpress.org/news/2024/09/wp-engine-banned/" rel="noopener noreferrer"&gt;WordPress.org blocked WP Engine's customers from receiving theme and plugin updates&lt;/a&gt;, leaving millions of sites potentially vulnerable to security issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 26:&lt;/strong&gt; In &lt;a href="https://youtu.be/H6F0PgMcKWM?si=XdoW49Y96FbfLWnR&amp;amp;t=801" rel="noopener noreferrer"&gt;an interview with ThePrimeagen&lt;/a&gt;, Mullenweg says that Automattic is using trademark law against WP Engine because there's no law that says that WP Engine has to contribute back. He elaborated on this point in an &lt;a href="https://youtu.be/OUJgahHjAKU?si=0X_Js5MHxVxMMHIA&amp;amp;t=1880" rel="noopener noreferrer"&gt;interview with Theo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;October 2:&lt;/strong&gt; &lt;a href="https://wpengine.com/wp-content/uploads/2024/10/Complaint-WP-Engine-v-Automattic-et-al.pdf" rel="noopener noreferrer"&gt;WP Engine sued Automattic&lt;/a&gt;. The lawsuit alleged Mullenweg attempted extortion against WP Engine's CEO.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;October 5:&lt;/strong&gt; Automattic tweets that they have "responsibly disclosed" a security vulnerability in Advanced Custom Fields to WP Engine. However, WP Engine was blocked from uploading their plugins to WordPress.org. Further, announcing a discovered vulnerability before it is fixed is out of line with &lt;a href="https://automattic.com/security/" rel="noopener noreferrer"&gt;Automattic's security policy&lt;/a&gt; and &lt;a href="https://wordpress.org/about/security/" rel="noopener noreferrer"&gt;WordPress's security policy&lt;/a&gt;. It also goes against the spirit of responsible disclosure. The tweet has since been deleted, but screenshots can be found on &lt;a href="https://x.com/kentbye/status/1842650847848841250" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and the tweet's data was captured by the &lt;a href="https://web.archive.org/web/20241005170538/https://twitter.com/automattic/status/1842612123488473341" rel="noopener noreferrer"&gt;Wayback Machine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;October 12:&lt;/strong&gt; Automattic takes control of the Advanced Custom Fields plugin listing, rebranding it as &lt;a href="https://wordpress.org/news/2024/10/secure-custom-fields/" rel="noopener noreferrer"&gt;Secure Custom Fields&lt;/a&gt;. This was done vaguely under the guise of security-related concerns. ACF had already patched the vulnerability, but WordPress.org had locked them out of publishing the updates. The transition from ACF to SCF would happen automatically, potentially without users understanding that the plugin is not from the original author. If viewed through the right lens, this could be seen as a supply chain attack. Meanwhile, the rebranded plugin &lt;a href="https://wordpress.org/support/topic/not-the-plugin-i-originally-installed-and-i-didnt-approve-this-one/" rel="noopener noreferrer"&gt;broke sites in the wild&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The preceding timeline excludes the WordPress Twitter &lt;a href="https://x.com/WordPress/status/1845121130207535524" rel="noopener noreferrer"&gt;bullying community members&lt;/a&gt;, Mullenweg's now-deleted &lt;a href="https://archive.ph/7ZRbY" rel="noopener noreferrer"&gt;tirade against DHH&lt;/a&gt;, or the &lt;a href="https://wptavern.com/wordpress-org-login-gets-mandatory-affiliation-checkbox-following-wp-engine-dispute" rel="noopener noreferrer"&gt;checkbox ordeal&lt;/a&gt;. You can find those and more on &lt;a href="https://designkitchen.dev/what-in-the-worldpress/" rel="noopener noreferrer"&gt;What in the WorldPress?&lt;/a&gt;, &lt;a href="https://bullenweg.com/" rel="noopener noreferrer"&gt;bullenweg.com&lt;/a&gt; and &lt;a href="https://mullenweg.wtf/" rel="noopener noreferrer"&gt;mullenweg.wtf&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  WordPress impact
&lt;/h2&gt;

&lt;p&gt;What will the result of this feud between WordPress and WP Engine be? It will be the total vindication of the open source alarmists in the late 90s and early 2000s. Steve Ballmer is smiling right now, believing that he was right to call Linux and the GPL a cancer.&lt;/p&gt;

&lt;p&gt;In the short term, both WordPress and WP Engine will probably take small hits in adoption as a result of this "scorched earth nuclear" conflict. But there currently aren't great alternatives to WordPress or WP Engine: for better or worse, both are best-in-class &lt;em&gt;for the moment&lt;/em&gt;. And migrating existing sites off of WordPress or WP Engine won't pencil out for many companies in the short-term, even though there is an ecosystem of alternative content management systems and hosting providers.&lt;/p&gt;

&lt;p&gt;However, migrating sooner rather than later might make sense for some sites. &lt;a href="https://www.whitehouse.gov/" rel="noopener noreferrer"&gt;WhiteHouse.gov&lt;/a&gt; is a WordPress site, and WordPress.org's capricious security policies could cause a vulnerability that allows a bad actor to post on the site that the US has declared war on China and is dropping bombs within the hour. What happens next? Is WordPress really &lt;em&gt;so good&lt;/em&gt; that it's worth that risk?&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing tides in OSS
&lt;/h2&gt;

&lt;p&gt;The security risks that came with open source software were generally worth it to companies because of the associated cost savings. Open sourced licensed software was often "free as in beer," or it at least had predictable pricing. The WordPress conflict upends that predictability: there is now a prominent case study of a major open source maintainer allegedly attempting to extort a CEO for money once their company got big. CEOs don't love unpredictable cost structures, and they &lt;em&gt;really&lt;/em&gt; don't love being extorted.&lt;/p&gt;

&lt;p&gt;Let's take a step back and look at the open source ecosystem outside of content management systems. It's not a stretch to think that there are thousands of risk-averse CTOs watching the utter chaos of the WordPress conflict unfold and seriously reconsidering using Node/Rust/Elixir/etc for their next greenfield projects. Some of these folks were around when companies first started adopting open source software like Linux, Apache, MySQL, and PHP in their stacks. There was incredible skepticism that free software could compete with companies that &lt;strong&gt;sold&lt;/strong&gt; developer tools as their core business. On top of that, the backdrop for this WordPress conflict is years of OSS license rug pulls from Redis, Terraform, Elasticsearch, and others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft will win, open source will lose
&lt;/h2&gt;

&lt;p&gt;There's an old tech phrase: "No one ever got fired for buying IBM." &lt;/p&gt;

&lt;p&gt;IBM may not have always been the best option and it certainly wasn't the cheapest, but it was a safe bet. If you're an enterprise CTO looking for the programming equivalent of that safe bet, what do you buy? Microsoft.&lt;/p&gt;

&lt;p&gt;The result of this WordPress debacle will almost certainly be a gentle migration away from exciting open source technologies back towards tried-and-true corporate frameworks like Microsoft ASP.NET. Those apps will run on Microsoft IIS servers, sparing us the cancer of open source software like Linux. Matt Mullenweg has probably done more for Adobe Experience Manager in the past month than their marketing team has done in the past year. Mullenweg has given Oracle sales reps one more talking point about why their clients should stay locked into their safe contracts. In this holy war for open source, we've seen how fragile open source is. Proprietary tech companies will use their lack of BDFL as a selling point.&lt;/p&gt;

&lt;p&gt;The more that WordPress "wins" against WP Engine, the more that open source will lose trust of the people who depend on it. I hope you like writing C# and deploying on Azure.&lt;/p&gt;

&lt;p&gt;For the sake of a future where open source is still a viable option, this conflict must end quickly, and WP Engine must win. An enormous amount of damage has already been done, but shaking down users of your free software because they became successful is a precedent than open source software might not survive in the current tech landscape.&lt;/p&gt;

</description>
      <category>wordpress</category>
    </item>
    <item>
      <title>Mock functions in individual tests using Jest</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Sun, 04 Aug 2024 01:31:46 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/override-functions-in-individual-tests-using-jest-dp5</link>
      <guid>https://dev.to/tylerlwsmith/override-functions-in-individual-tests-using-jest-dp5</guid>
      <description>&lt;p&gt;Sometimes you want to mock a function in some tests but not others. Sometimes you want to supply different mocks to different tests. Jest makes this tricky: its default behavior is to override a package's function for a whole test file, not just a single test. This seems odd if you've used flexible tools like Python's &lt;code&gt;@patch&lt;/code&gt; or Laravel's service container.&lt;/p&gt;

&lt;p&gt;This post will show you how to mock functions for individual tests, then fallback to the original implementation if no mock was provided. Examples will be given for both CommonJS and ES modules. The techniques demonstrated in this post will work for both first-party modules and third-party packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  CommonJS vs ES Modules
&lt;/h2&gt;

&lt;p&gt;Since we'll be covering multiple module systems in this post, it's important to understand what they are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CommonJS&lt;/strong&gt; (abbreviated CJS) is the module system in Node.js. It exports functions using &lt;code&gt;module.exports&lt;/code&gt; and imports functions using &lt;code&gt;require()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CommonJS export &lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;greet&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CommonJS import&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUsersList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./greet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ES modules&lt;/strong&gt; (abbreviated ESM) is the module system that's used by the browser. It exports functions using the &lt;code&gt;export&lt;/code&gt; keyword and imports functions using the &lt;code&gt;import&lt;/code&gt; keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ES module export&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ES module import&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;greet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./greet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most frontend JavaScript developers use ES modules at the time of writing this post, and many server-side JS devs use them as well. However, CommonJS is still the default for Node. Regardless of which system you use, it is worth reading the whole article to learn about Jest's mocking system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocking a single exported function with CommonJS
&lt;/h2&gt;

&lt;p&gt;Typically a CommonJS file will export their modules using object syntax, like shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CommonJS export &lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;greet&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it is also possible to export a function by itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CommonJS export &lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;I wouldn't necessarily recommend doing this in your own code:&lt;/strong&gt; exporting an object will give you fewer headaches while developing your application. However, it is common enough that it's worth discussing how to mock a bare exported function in CommonJS, then fallback to the original if a test does not provide its own implementation.&lt;/p&gt;

&lt;p&gt;Let's say we have the following CommonJS file we'd like to mock during tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cjsFunction.js&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;original&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could mock it in our tests using the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testFunc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cjsFunction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cjsFunction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requireActual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cjsFunction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can override the implementation for a single test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock implementation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock implementation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can override the return value for a single test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock return value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock return value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns the original implementation when no overrides exist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;original&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;When we call &lt;code&gt;jest.mock("./cjsFunction")&lt;/code&gt;, this replaces the module (the file and all of its exports) with an auto-mock (&lt;a href="https://jestjs.io/docs/jest-object#jestmockmodulename-factory-options" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). When an auto-mock is called, it will return &lt;code&gt;undefined&lt;/code&gt;. However, it will provide methods for overriding the mock's implementation, return value, and more. You can see all the properties and methods it provides in the &lt;a href="https://jestjs.io/docs/mock-function-api" rel="noopener noreferrer"&gt;Jest Mock Functions documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can use the mock's &lt;code&gt;mockImplementation()&lt;/code&gt; method to automatically set the mock's implementation to the original module's implementation. Jest provides a &lt;code&gt;jest.requireActual()&lt;/code&gt; method that will always load the original module, even if it is currently being mocked. &lt;/p&gt;

&lt;p&gt;Mock implementations and return values are automatically cleared after each test, so we can pass a callback function to Jest's &lt;code&gt;beforeEach()&lt;/code&gt; function that sets the implementation of the mock to the original implementation before each test. Then any tests that wish to provide their own return value or implementation can do that manually within the test body.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocking CommonJS when exporting an object
&lt;/h2&gt;

&lt;p&gt;Let's say that the code above had exported an object instead of a single function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cjsModule.js&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;original&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our tests would then look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cjsModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cjsModule&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restoreAllMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can override the implementation for a single test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;testFunc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock implementation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock implementation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can override the return value for a single test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;testFunc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock return value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock return value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns the original implementation when no overrides exist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;original&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can spy on calls while keeping the original implementation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;testFunc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;original&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cjsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;testFunc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;jest.spyOn()&lt;/code&gt; method allows Jest to record calls to a method on an object and provide its own replacement. This &lt;em&gt;only&lt;/em&gt; works on objects, and we can use it because our module is exporting an object that contains our function.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;spyOn()&lt;/code&gt; method is a mock, so its state must be reset. The &lt;a href="https://jestjs.io/docs/jest-object#jestspyonobject-methodname" rel="noopener noreferrer"&gt;Jest spyOn() documentation&lt;/a&gt; recommends resetting the state using &lt;code&gt;jest.restoreAllMocks()&lt;/code&gt; in an &lt;code&gt;afterEach()&lt;/code&gt; callback, which is what we did above. If we did not do this, the mock would return &lt;code&gt;undefined&lt;/code&gt; in the next test after &lt;code&gt;spyOn()&lt;/code&gt; was called.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocking ES modules
&lt;/h2&gt;

&lt;p&gt;ES modules can have default and named exports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// esmModule.js&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;original default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;original named&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what the tests for the file above would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;esmModule&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./esmModule&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restoreAllMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can override the implementation for a single test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock implementation default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;named&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock implementation named&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock implementation default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock implementation named&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;named&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can override the return value for a single test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock return value default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;named&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock return value named&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock return value default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock return value named&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;named&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns the original implementation when no overrides exist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;original default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;original named&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;This looks &lt;em&gt;almost&lt;/em&gt; the same as the previous CommonJS example, with a couple of key differences.&lt;/p&gt;

&lt;p&gt;First, we're importing our module as a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#namespace_import" rel="noopener noreferrer"&gt;namespace import&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;esmModule&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./esmModule&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then when we want to spy on the default export, we use &lt;code&gt;"default"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;jest&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;esmModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock implementation default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting ES module imports
&lt;/h2&gt;

&lt;p&gt;Sometimes when trying to call &lt;code&gt;jest.spyOn()&lt;/code&gt; with a third-party package, you'll get an error like the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    TypeError: Cannot redefine property: useNavigate
        at Function.defineProperty (&amp;lt;anonymous&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run into this error, you'll need to mock the package that is causing the issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;reactRouterDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ADD THIS:&lt;/span&gt;
&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requireActual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;__esModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;originalModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restoreAllMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code replaces the module with a Jest ES Module mock that contains all of the module's original properties using &lt;code&gt;jest.mocks&lt;/code&gt;'s factory parameter. The &lt;code&gt;__esModule&lt;/code&gt; property is required whenever using a factory parameter in &lt;code&gt;jest.mock&lt;/code&gt; to mock an ES module (&lt;a href="https://jestjs.io/docs/jest-object#jestmockmodulename-factory-options" rel="noopener noreferrer"&gt;docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If you wanted, you could also replace an individual function in the factory parameter. For example, React Router will throw an error if a consumer calls &lt;code&gt;useNavigate()&lt;/code&gt; outside of a Router context, so we could use &lt;code&gt;jest.mock()&lt;/code&gt; to replace that function throughout the whole test file if we desired:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requireActual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;__esModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;originalModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Dummy that does nothing.&lt;/span&gt;
    &lt;span class="nf"&gt;useNavigate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I hope this information is valuable as you write your own tests. Not every app will benefit from being able to fallback to the default implementation when no implementation is provided in a test itself. Indeed, many apps will want to use the same mock for a whole testing file. However, the techniques shown in this post will give you fine-grained control over your mocking, allowing you to mock a function for a single test in Jest.&lt;/p&gt;

&lt;p&gt;Let me know if I missed something or if there's something that I didn't include in this post that should be here.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>jest</category>
      <category>testing</category>
    </item>
    <item>
      <title>Get better autocomplete for Faker when using factory_boy</title>
      <dc:creator>Tyler Smith</dc:creator>
      <pubDate>Sat, 27 Jul 2024 05:03:24 +0000</pubDate>
      <link>https://dev.to/tylerlwsmith/get-better-autocomplete-for-faker-when-using-factoryboy-kki</link>
      <guid>https://dev.to/tylerlwsmith/get-better-autocomplete-for-faker-when-using-factoryboy-kki</guid>
      <description>&lt;p&gt;Before language server powered autocomplete was ubiquitous, it didn't matter if code was written in a way that made autocomplete easy. Popular text editors like Sublime weren't able to understand code enough to provide meaningful suggestions. Programmers either memorized the commands of their dependencies or they referenced the documentation.&lt;/p&gt;

&lt;p&gt;In 2024 autocomplete is everywhere, but our packages haven't necessarily made its job easy. Take this example from &lt;a href="https://factoryboy.readthedocs.io/en/stable/reference.html#faker" rel="noopener noreferrer"&gt;Factory Boy's documentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

    &lt;span class="n"&gt;arrival&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Faker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date_between_dates&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;date_start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;date_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;arrival&lt;/code&gt; attribute calls Faker's &lt;code&gt;date_between_dates()&lt;/code&gt; method. Unfortunately, &lt;code&gt;factory.Faker()&lt;/code&gt; does not tell you what Faker methods are available, and it won't tell you what parameters a Faker method accepts once you select one. This code mitigates the benefits that language servers provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autocompleting Faker in factories
&lt;/h2&gt;

&lt;p&gt;You can get autocomplete while using Faker with &lt;code&gt;factory_boy&lt;/code&gt; by wrapping a Faker call in &lt;code&gt;factory.LazyFunction()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Faker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

    &lt;span class="n"&gt;arrival&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LazyFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date_between_dates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;date_start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;date_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Functions passed to &lt;code&gt;factory.LazyFunction()&lt;/code&gt; evaluate when the factory creates an instance. We wrap our faker call in a lambda so we can provide it arguments. And your language server can show you what parameters &lt;code&gt;fake.date_between_dates()&lt;/code&gt; accepts.&lt;/p&gt;

&lt;p&gt;There's less set-up when the faker function doesn't need any arguments. Here is how you could use &lt;code&gt;factory.LazyFunction()&lt;/code&gt; with Faker's &lt;code&gt;first_name()&lt;/code&gt; and &lt;code&gt;last_name()&lt;/code&gt; methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Faker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

    &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LazyFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LazyFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The biggest advantage of using &lt;code&gt;factory.LazyFunction()&lt;/code&gt; is that it doesn't just work for Faker: you can use it to get &lt;code&gt;factory_boy&lt;/code&gt; to call any function. The resulting code is a little longer than it would be if we had used &lt;code&gt;factory.Faker()&lt;/code&gt;, but the assistance from the language server is worth it to me. Without it, I might need to constantly check documentation.&lt;/p&gt;

&lt;p&gt;If it's too much to type, you can alias it to something shorter such as &lt;code&gt;LazyFn&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LazyFunction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;LazyFn&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Faker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

    &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LazyFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LazyFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
