Chris Padilla/Blog


My passion project! Posts spanning music, art, software, books, and more. Equal parts journal, sketchbook, mixtape, dev diary, and commonplace book.


    Testing Encourages Refactoring

    I came across hard evidence that testing leads to well designed code this week. Taking the time to test old code is helping me decouple my JavaScript!

    I'm using a NextJS E-commerce site as a playground for practicing writing tests. On the site, I have an item order page for each item on a menu. On the page, you can customize the item, select quantity, and then add them to your cart.

    The component tree structure looks something like this:

    <ItemDisplayPage>
        <CartItemForm>
            <CustomizationDisplay>
            <AddItemToCart>
                <ButtonWithPrice />
    
            ...

    ItemDisplayPage is the page level component, and ButtonWithPrice is the lowest child in this form. Everything else in between handles a spectrum of logic relating to the form.

    The Rookie Mistake

    This app is a 2-year old project, a portfolio piece written before I was full time in software. So there were some odd design choices.

    This is the big one: My onSubmit function was in <ButtonWithPrice/>. Yikes!

      const handleSubmit = (values) => {
        dispatch({
          type: 'ADD_TO_CART',
          payload: {
            ...values,
          },
        });
    
        router.push('/');
      };

    It's doable because, with hooks and global state, I can make that call all the way from the button and it is technically possible.

    That very much tightly couples my form logic with the submit button. The button should really only be concerned with rendering price, adjusting quantity, and then firing the handleSubmit method — NOT declaring it, though.

    Testing Encourages Refactoring

    At the time, it was no big deal. The app worked, tangled as it may have been!

    When it came to testing the code, though, doing so was challenging.

    I wanted to write an integration test the verifies that, on submit and after some interaction, I'm getting the right data saved to my Redux store. I want to do this from CartItemForm since that's a logical container for all of the interactions and form submission.

    Here's a sketch of what that test looked like:

    /**
     * @jest-environment jsdom
     */
    import React from 'react';
    import * as reactRedux from 'react-redux';
    import { render, cleanup, waitFor, fireEvent } from '@testing-library/react';
    import CartItemForm from '../components/CartItemForm';
    
    test('<CartItemForm />', async () => {
        const rendered = render(
        <CartItemForm
          item={chipsAndGuac}
          itemID={'5feb9e4a351036315ff4588a'}
        />
      );
    
        ...
    
      const spiceOptions = await rendered.findAllByTestId('option');
      const firstOption = spiceOptions[0];
    
      fireEvent.click(firstOption);
    
      const addItemButtonElm = await rendered.findByTestId('add-item');
    
      expect(addItemButtonElm.className.includes('selected'));
    
      fireEvent.click(addItemButtonElm);
    
      expect(handleSubmit).toBeCalledTimes(1);
      expect(handleSubmit).toBeCalledWith(formExpectedValue);
    
    }

    The gist:

    1. I'm rendering CartItemForm with React Testing Library
    2. I'm simulating user interaction by selecting a spice level in the form.
    3. I'm clicking the submit button
    4. I'm asserting if handleSubmit has been called and if it has certain values.

    With this test written, here's the problem - how do I mock handleSubmit and read the values? I simply can't with the way my component is structured!! I can't drill down and mock from this level. Even if I rendered my button component, the submit method is still within the component and not easily reachable. It has to be extracted in some way.

    So, ultimately I moved the handleSubmit declaration up to ItemDisplayPage and prop drilled from there.

    That allowed me to mock and pass the mocked handleSubmit method this way:

      const handleSubmit = jest.fn();
    
      const rendered = render(
        <CartItemForm
          item={chipsAndGuac}
          itemID={'5feb9e4a351036315ff4588a'}
          onSubmit={handleSubmit}
        />
      );

    What do I have as a result now?

    • A passing test ✅
    • More flexible, organized code ✅
    • Bonus: I can more confidently refactor later, knowing I have tests in place 💯

    I can see why there are cultures around testing first. WHen using libraries that fall on the spectrum of being opinionated, it guides your code to being more resilient. The upfront cost of setup is paid off in the long run with easily changeable code.


    Testing Organizes Code

    Rebecca Murphey has a fantastic. talk on front end testing. It's all done in JQuery, and the principles still apply really nicely to react applications.

    My favorite takeaways:

    1. Testing allows for a clear explanation of what consumers of your code can expect. Rebekah says this is like documentation, but even better! Tests will actually verify that the code does what it says it will, unlike documentation.
    2. Testing allows you to write methodically. Writing the code is easy once you've written the expectations. This is further validation for testing as a process of clear thinking. Put another way: Measure twice, cut once.
    3. Testing old code illuminates tight coupling. Much of the talk is diving into an example of a long file of entangled responsibilities. Server requests, rendering data, managing state, all in one function. When you sit down to write a test for this, the coupling is quickly illuminated.

    Jest Supports This Systematically

    That last point is one I'm coming up against in my own codebase.

    This site keeps all it's content in markdown. So I have a looong file with methods for grabing that data:

    // api.js
    
    const postsDirectory = join(process.cwd(), '_posts');
    
    export function getPostSlugs() {
      return fs.readdirSync(postsDirectory);
    }
    
    export function getPostBySlug(slug, fields = []) {
      const realSlug = slug.replace(/\.md$/, '');
      const fullPath = join(postsDirectory, `${realSlug}.md`);
      if (!fs.existsSync(fullPath)) return false;
      const fileContents = fs.readFileSync(fullPath, 'utf8');
      const { data, content } = matter(fileContents);
    
      const items = {};
    
      // Ensure only the minimal needed data is exposed
      fields.forEach((field) => {
        if (field === 'slug') {
          items[field] = realSlug;
        }
        if (field === 'content') {
          items[field] = content;
        }
    
        if (typeof data[field] !== 'undefined') {
          items[field] = data[field];
        }
      });
    
      return items;
    }
    
    export function getAllPosts(fields = [], options = {}) {
      const slugs = getPostSlugs();
      let posts = slugs
        .map((slug) => getPostBySlug(slug, fields))
        // Filter false values (.DS_STORE)
        .filter((post) => post)
        // sort posts by date in descending order
        .sort((post1, post2) => (post1.date > post2.date ? -1 : 1));
    
      if (options.filter) {
        posts = posts.filter(options.filter);
      }
    
      if (options.limit) {
        posts = posts.slice(0, options.limit);
      }
    
      return posts;
    }
    
    . . .

    If I wanted to test getAllPosts, this is already difficult on a file-organization level. I can't very well mock the method getPostSlugs because with Jest you can only mock external packages.

    I suppose this, in practice, isn't a terribly tight coupling. They are separate methods. But it has already illuminated an opportunity to break up this file to more closely follow a MVC model of organization. A strange paradigm to take on with the absence of a database here, but taking a step towards it lends to the first point at the top of this post - we get clarity in expectations of how this app is working. A big win already.


    Generating a Sitemap in NextJS

    I wanted to learn how to add search functionality to a NextJS Markdown blog. That's my blog and I'm starting to accumulate a lot of posts here! So I took to Google, and what did I find? Why, I found Julia Tan's post on "How To Add Search Functionality to a NextJS Markdown Blog".

    I've been pretty happy with blogging being a more personal, long form means of keeping a dev journal. Recently, though, blogging has helped me find my people along the way. And that's really exciting!

    When a Google search sent me to a personal site and not a Medium article or Stack Overflow forum, that got me pretty excited. I'm not looking to growth hack my blog or become a domain authority, but I do want to make it easier for folks to find these posts.

    So begins an SEO cleanup!

    The Current Situation

    My site currently checks off the boxes for the bare bone necessities here: I have meta tags for pages and I'm using Semantic HTML for easy parsing.

    I tried Googling a few of my own articles with some disappointing results. "Express React Chris Padilla" at the time brought up my landing page, but not my article on it.

    I went through a few considerations of why this might be:

    1. I don't track this, but I'm confident my blog doesn't get a lot of traffic at the moment. :)
    2. I don't have keyword authority on Express or React, sure. But I have at least more authority with "Chris Padilla" in a tech context, I would think!
    3. I considered my URL structure. I don't keep any subdirectories, but I learned that this doesn't really have a large sway on SEO.

    Sitemaps

    No better source to ask about this than Google themselves. And a bit of looking brought me to sitemaps and why I might need one.

    A Sitemap is largely what it sounds like: It's an XML file that outlines the pages on your site. Bots will use this to ensure all your pages are crawled. Here's mine.

    Here's Google documentation on why you might need one:

    • Your site is large. Generally, on large sites it's more difficult to make sure that every page is linked by at least one other page on the site. As a result, it's more likely Googlebot might not discover some of your new pages.
    • Your site is new and has few external links to it. Googlebot and other web crawlers crawl the web by following links from one page to another. As a result, Googlebot might not discover your pages if no other sites link to them.

    "Large" to Google probably means hundreds or thousands of pages, but my page count is crawling day by day. More important is probably that this is a new site and I'm not breaking the internet with these posts.

    Generating with NextJS

    Next has great documentation on this and even provides code for generating your own sitemap.

    I had to tweak mine just a bit to provide a couple of handy tags:

    • <priority>: on a scale from 0 to 1.0, how important is this page? Landing page is 1.0. My music page is 0.9.
    • <changefreq>: This site is alive! This is how to let bots know the interval it typically updates. My blog page would be daily and my now page would be monthly.

    Here's what those tweaks look like in NextJS's generateSiteMap function:

    function generateSiteMap(slugs) {
      return `<?xml version="1.0" encoding="UTF-8"?>
       <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
         <!--Manual URL's-->
         <url>
           <loc>${BASE_URL}</loc>
           <priority>1</priority>
           <changefreq>daily</changefreq>
         </url>
         ${slugs
           .map((slug) => {
             if (slug === 'blog') {
               return `
                <url>
                  <loc>${`${BASE_URL}/${slug}`}</loc>
                  <priority>0.9</priority>
                  <changefreq>daily</changefreq>
                </url>
              `;
             } else if (slug === 'now' || slug === 'music') {
               return `
                 <url>
                   <loc>${`${BASE_URL}/${slug}`}</loc>
                   <priority>0.9</priority>
                   <changefreq>monthly</changefreq>
                 </url>
               `;
             } else {
               return `
                <url>
                  <loc>${`${BASE_URL}/${slug}`}</loc>
                </url>
              `;
             }
           })
           .join('')}
       </urlset>
     `;
    }

    Getting those values is business as usual for NextJS: write a getServerSideProps method.

    export async function getServerSideProps({ res }) {
      // We make an API call to gather the URLs for our site
      const pages = getAllPages();
      const posts = getAllPosts(['slug']);
      const albums = getAlbums();
      const dynamicSlugs = [...albums, ...posts].map(
        (contentObj) => contentObj.slug
      );
    
      const slugs = [...pages, ...dynamicSlugs];
    
      // We generate the XML sitemap with the posts data
      const sitemap = generateSiteMap(slugs);
    
      res.setHeader('Content-Type', 'text/xml');
      // we send the XML to the browser
      res.write(sitemap);
      res.end();
    
      return {
        props: {},
      };
    }

    Funny to me is that you still have to export a default function. The res.write() and res.end() method above handle sending the XML file. But, I suppose Next still needs to see a React component for this to work happily:

    function SiteMap() {
      // getServerSideProps will do the heavy lifting
    }
    
    export default SiteMap;

    And there you have it! Here's hoping you've reached the end of this article after searching "Generating a Sitemap in NextJS."


    Blog to Find Your People

    Austin Kleon shared Henrik Karlsson's "A blog post is a very long and complex search query to find fascinating people and make them route interesting stuff to your inbox." in one of his latest posts

    A blog post is a search query. You write to find your tribe; you write so they will know what kind of fascinating things they should route to your inbox. If you follow common wisdom, you will cut exactly the things that will help you find these people. It is like the time someone told the composer Morton Feldman he should write for “the man in the street”. Feldman went over and looked out the window, and who did he see? Jackson Pollock.

    ...You will write essays that almost no one likes…. Luckily, almost no one multiplied by the entire population of the internet is plenty if you can only find them.

    Kleon concludes that "This is really a great summary of the best thing that writing and sharing your work can do for you." Just as he outlines in moments through Show Your Work!

    This is what drew me back to sharing online and firing up this blog. After reading Digital Minimalism by Cal Newport, I had taken on a Luddite's mentality to putting things out online. I swore to only doing in person socializing and purely offline means of communication.

    It was great in many ways! Our brains are really wired for the physical connections of a dinner date or playing in a community band.

    But the internet is such an amazing medium for connecting with kindred spirits.

    From the Neopets forums to sending an email after reading someone's blog post, our work connects us. One of the best reasons to practice any art is that it's a form of communication with others in an elevated craft. That is amplified by making that connection with people beyond our local tribe.

    Having a means of genuine connection with a global community. How rad is that?

    This blog hasn't been going long, but I've already made some great friends just by saying hello and keeping up with each other's posts on our blogs.

    Added bonus for blogging in particular - It's insulated from likes, from performance metrics, and it's a glove that fits the hand of the artist, vs cramming them into a box.

    Romanticised? Maybe. But I think it's pretty darn special!


    Chunky Head Construction

    Studies and experiments on Andrew Loomis' Fun With a Pencil! Lots of chonky cheeks. 🙉

    Big round boys

    ⚽️ Start here:

    Faceless round boys

    🙊 End up here:

    Angling these guys from top and bottom view is hilarious


    Fingerstyle Prelude

    Listen on Youtube

    Getting fancy with fingerstyle! 🖐


    Hosting Images for chrisdpadilla dot com

    My site is turning into a lot of images. I haven't really considered the way they're handled on my site very thoroughly until now.

    My current strategy is hosting on an AWS S3 bucket and using the Image component from Next JS. With the component, Next handles the image optimization. So I can upload images as is (usually 2mb big) and Next will dynamically optimize for the resolution requested.

    It's worked great on the pages I'm generating on the server. But I recently found out the component doesn't run on statically generated pages. For me that means all my blog posts aren't having their images compressed.

    I serve up an RSS feed, and that approach won't quite do there either. The next component would be cumbersome while generating the feed.

    Essentially, I need a non-Next solution for taking a moderately high quality image from my host and dynamically serving up a light-weight image.

    My Options

    1. I know Wordpress has plugins for this sort of thing. But it's not really reasonable to migrate my site just for this sort of thing.
    2. TinyPNG is one of the plugins, and they have a CDN and API solution. They'll even save your images to S3 after optimizing. A bit too involved for my taste here, though.
    3. I used Sanity for AC: New Murder. They had a nice library for querying images in the exact dimensions you needed, and this could be done dynamically. Getting warmer, but I'm not in need of a full-blown CDN.
    4. Cloudinary has the same on-the-fly image optimizing through URL that Sanity has, and a very generous free tier.

    So I'm turning to Cloudinary. And so far, so good! I've created a named transform so that all I have to do is add a t_optimize param to the url to get a resized, compressed image. I can get more granular with it from here, but for my needs, it feels like this gets me most of the way there to optimized images.

    https://res.cloudinary.com/cpadilla/image/upload/t_optimize/chrisdpadilla/albums/spring.jpg

    And there we go! There's support to do this dynamically with JavaScript, and I have a way of bringing the images in to RSS without worrying about a heavy load time.


    New Album — Spring ☀️

    These books are like floating daydreams

    Bring on the clear skies and open windows!!

    Purchase on 🤘 Bandcamp and Listen on 🙉 Spotify.


    Keeping a Junk Drawer

    I admit to being a notes nerd.

    I keep most of my dev notes, blog ideas, To-Do lists, and research notes in plain text. I have neat little folders on my computer to keep them in place. I even use tags and record dates on my notes!

    I day dream about implementing more organized systems like the Zettelkasten Method (as Idiscovered through the Draftsmen podcast) or using Wiki software (as advocated for by Andy Hunt in Pragmatic Thinking & Learning).

    Those dreams usually crash on the reality of the non-linear and subject-hopping pattern my brain runs un.

    Essentially - With notes, not EVERYTHING can be organized.

    What helps keep a notes nerd like me at ease with this is an idea of a junk drawer.

    Daniel Levitin in his book "The Organized Mind" closes the book with how our brains are naturally good at categorizing, and the "Miscellaneous" category totally counts as it's own category.

    This manifests in a few way for me: it's my hand-written, physical notepad where I write stream-of-conciouse-notes through the day.

    Study notes from Drawabox

    I'm also starting most days with Julia Cameron's brain dump method of Morning Pages.

    Even in my markdown system! I have a "Links.md" file where I grab links I might want later. If I notice a category is emerging, I can pull out a new markdown file and place them. But, for most things, it's just fine that I capture them and let them float down the river as I continue to add links.

    date: 2023-01-18
    url: https://www.youtube.com/watch?v=vUSWfeY7xAs&ab_channel=DrawlikeaSir
    tags: art
    notes: Resource for how to draw hands.
    
    date: 2023-01-13
    url: https://www.youtube.com/watch?v=JFg74x5bStg&ab_channel=PointLobo
    tags: music, production
    notes: Tips on creating music with a director. From Infinite Journeys, a CG animation project. Point Lobos is the artist. My favorite tips are 1. Record midis before recording actual tracks when possible. and 2. Don't get emotionally attached to your music. Be ready to chop it up. It becomes a new piece.
    
    date: 2023-01-13
    url: https://blog.jim-nielsen.com/2019/i-love-rss/
    tags: tech
    notes: Jim Nielsen's Favorite blogs

    Even with music writing - I have an improvisatory, molecular ideas folder. "Cool baseline.mp3", "Pretty Piano Flourish.mp3". And then from there, some of those get pulled into fleshed out songs.

    The junk drawer keeps me loose in the same way gesture drawing does. It keeps me quick to jot something down, with no judgement of the process in the same way I would be if I were writing in a blog CRM or a tweet.


    What Over-Mocking Looks Like

    TIL!

    I'm testing a method that grabs the latest post tagged with "Hap." I want to verify that the post will return the latest result.

    import fs from 'fs';
    import { getLatestHap } from '../lib/api';
    
    jest.mock('fs');
    
    jest.mock('../lib/markdownAccess', () => ({
      getPostBySlug: () => ({ slug: '2023-01', tags: ['Notes', 'Haps'] }),
      getPostSlugs: () => ['2023-01'],
    }));
    
    test('getLatestHap', () => {
      const sampleMarkdownFile = { slug: '2023-01', tags: ['Notes', 'Haps'] };
      fs.readdirSync.mockResolvedValue([
        'postone.md',
        'posttwo.md',
        'postthree.md',
      ]);
      const result = getLatestHap();
      expect(result).toStrictEqual(sampleMarkdownFile);
    });

    I had to mock a few things to abstract their use:

    • We don't need to test fs, so I mocked that.
    • getPostSlugs is essentially one level above fs, so I mocked this
    • getPostBySlug similarly is just a wrapper around fs after being passed a slug, so I mocked that.

    So there! I've mocked all that doesn't need testing.

    I then realized... what's left for me to test here?

    This is the function I'm testing:

    export const getLatestHap = (fields = ['slug', 'tags']) => {
      const posts = getAllPosts(fields);
      const latestHap = posts.find((post) => post.tags.includes('Haps'));
      return latestHap;
    };

    I've mocked this to the point where I'm essentially testing the native find Array method. Whoops.

    Mostly Integration

    This is me learning from experience what Guillermo Rauch tweeted and what Kent C. Dodds expands upon:

    Write tests. Not too many. Mostly integration.

    Here I've nailed the first one, started to drift from the second point, and need to move towards the third to make my way out.

    This test is trite because it's not testing any logic. This would largely be better as an integration test, or perhaps even an E2E test in Cypress. In an E2E test, I would be grabbing lots of different cases along the way - are my pages loading, is data coming in properly from the source, and does it ultimately render correctly.

    I've been learning about Jest, so this is a case of "Every problem is a nail when you have a hammer."

    Ah well, lesson learned!

    E2E Version: A better way

    Here's what that test looks like in Cypress:

    import { BASE_URL, BASE_URL_DEV } from '../../lib/constants';
    
    const baseUrl = process.env.NODE_ENV === 'production' ? BASE_URL : BASE_URL_DEV;
    
    Cypress.on('uncaught:exception', (err, runnable) => {
      return false;
    });
    
    describe('Verify Now Page Renders Blog Page', () => {
      const blogURL = `${baseUrl}/now`;
      it('Loads the blog page with post', () => {
        cy.visit(blogURL);
        const markdownContainerElm = cy.get('.markdown');
        markdownContainerElm.should('be.visible');
      });
    });

    Here's what I'm doing:

    1. Allowing for an unhandled minor error being thrown in the console with Cypress.on('uncaught:exception', ...)
    2. Visiting the now page.
    3. Checking for a .markdown element.
    4. Verifying it exists. If no result is found by getLatestHap, a 404 would render.

    Same amount of code, but here's what was tested along the way:

    • getLatestHap is grabbing a markdown file
    • My React component NowPage is receiving the post
    • The page is rendering the result by creating a div with the class of markdown instead of a 404.

    That's way better mileage over my trite unit test from before!


    Vehicles as Eras of Life

    I'm still enjoying Terry Pratchett's Biography.

    One of my favorite things about it so far is that, amidst all the interesting life stories, milestones, new jobs, new places lived, the characters Terry came across — with all that to talk about, there's still a non-trivial number of words dedicated to talking about the different cars and bikes Terry drove around.

    It's one of my first biographies that I'm giving a read, so maybe this is normal? Maybe I skimmed those parts of Will Smith's Biography? Does the Steve Jobs Biography lament that he never made it to driving a Tesla?

    I'm only partway through, but I don't think Terry was a motor buff, so that's not why, right?

    It's funny to me — but it's relatable. It's a shared experience — those fond memories of "The 100k miles+ hand me down I took to college" or "The Toyota we shared while we saved up money to make it through grad school."

    Some folks can mark major periods of their entire lives with their vehicles.

    Here's Terry's timeline so far as I've read, ages estimated by context:

    On a related note, I bought a new phone the other day. I have to admit, though, it doesn't have the same "The start of a new era" effect that cars do.


    Still Life and Plant Construction

    Miranda and I did some still life together. Can you tell which one is her's?? (It's the really good one!) 🍐

    Plant

    Some plant construction studies in the mix, too

    Plant

    Plant

    Plant


    Aurora Borealis

    Listen on Youtube

    The pieces in the Faber method are so delightfully musical! Beats the Rubank wind instrument books by a long shot. 🌌


    The Best Time To Be an Autodidactic Polymath

    Autodidact is the 10 dollar word for saying someone is self taught. Polymath is the one for someone who's know-how spans multiple subjects.

    Wikipedia keeps a tidy list of Autodidacts, some of them are polymaths.

    Here are a few of my favorites:

    Libraries raised me. I don't believe in colleges and universities. I believe in libraries because most students don't have any money. When I graduated from high school, it was during the Depression and we had no money. I couldn't go to college, so I went to the library three days a week for 10 years. — Ray Bradbury

    Here's Terry Pratchett, a great autodidact, on his schooling:

    I didn't go to university. Didn't even finish A-levels. But I have sympathy for those who did.

    It comes up in Terry's biography that, late in life, he takes up professorship to explain to creative writing students all the reasons why you can't teach creative writing in a classroom.

    My absolute personal favorite has to be David Bowie: a "singer, musician, multi-instrumentalist, actor, and painter, only received a few singing lessons in the 1960s."

    A few others include Mark Twain, Charles Dickens, Heitor Villa-lobos. I've also written about Walt Stanchfield, Disney animator, mentor, musician, poet, writer, and teacher.

    Easier Than Ever

    Stories like this inspire me because it's where my mind bends. I'm always falling into learning something new. I also have trouble feeling like I can settle into one practice. I feel more fluid creatively when I have many different post boiling. So it's comforting to see legendary levels of fooling around, people stepping into different roles and mediums, while still bringing the same creative spirit to each.

    I taught myself web development, but then again, we're all teaching ourselves on the job. The learning never stops.

    The same is true in art and music. I went to music school, but my day to day musicing is a result of learning new instruments and styles everyday. SO MANY visual artists are largely self taught these days, myself included.

    Creative work today, in software or music, leads to constantly evolving inherently. We're also living in the absolute best time to learn new things all the time with having access to the internet. Y'know, the world's repository of knowledge.

    Because it's so easy to self teach, it's also easier than ever to be a multifaceted creative.

    These fancy words — Autodidact and Polymath — felt more like exceptions. Now, the thrilling thing to me is that we all can wear those fancy labels.


    Testing Data Fetching Components + Other Jest Gotcha's

    Testing Results of an API Call in React

    While running an integration test on a React component, we don't need to test the API. But, we are typically interested in testing that our results are rendering appropriately in the component. Juggling this type of integration test takes a bit of setup an specific steps:

    First, we need to mock the global fetch package. Jest Fetch Mock handles this nicely for us. We can set that globally like so:

    import React from 'react';
    import { render, cleanup, waitForElement } from '@testing-library/react';
    import MovieDetail from './MovieDetail';
    
    global.fetch = require('jest-fetch-mock');

    Within our test, we'll want to mock the call and return fake date:

    test('<MovieDetail />', async () => {
      fetch.mockResponseOnce(
        JSON.stringify({
          id: 'hi',
          title: 'Cool Movie',
        })
      );
    
      ...
    
     }

    Note the async! We'll need to await later within our function.

    From there, we will need to wait for the effects to kick in from the API call and verify the DOM changed with the new data:

      const { debug, getByText, getByTestId } = render(<MovieDetail match={match} />);
      await waitForElement(getByText('Cool Movie'));
      expect(getByTestId('movie-title').textContent).toBe('Cool Movie');

    I found on Tania Rascia's post on testing that there's a slick one-liner way to do this with findBy, which rolls waitFor and getBy into one assertion:

    expect(await findByTestId('movie-title').textContent).toBe('Cool Movie');

    Violà! Here's the full integration test:

    import React from 'react';
    import { render, cleanup, waitForElement } from '@testing-library/react';
    import Film from './Film';
    
    global.fetch = require('jest-fetch-mock');
    
    test('<Film />', async () => {
         fetch.mockResponseOnce(
            JSON.stringify({
                 id: 'hi',
                title: 'Cool Movie',
        })
      );
    
         const { getByTestId } = render(<MovieDetail match={match} />);
        expect(await getByTestId('movie-title').textContent).toBe('Cool Movie');
    
     }

    Other Floating Notes

    Install types for that sweet, sweet autocomplete:

    {
         "devDependencies": {
            "@testing-library/jest-dom": "^5.16.5",
            "@types/jest": "^29.4.0",
        }
    }

    Difference between getBy vs queryBy

    queryBy It's a "Maybe." Maybe it exists, maybe it doesn't. queryBy will check if it's there or not. You can use queryBy to assess later if it's a good or bad thing that the element is or isn't there.

    getBy, however, will strictly find an element and error out if it doesn't find the element, automatically failing your test.

    Code Coverage

    $ jest --coverage

    Generates a report that shows percentage of your codebase covered. The general wisdom is to not overly rely on this as your source of truth. You can have complete coverage with breaking tests, or even coverage with passing tests and a broken user experience.

    Testing A Component That Uses React Router

    A common gotcha is working with React Router's match props that get passed into page-level components. If hitting errors when testing a component, the way through is 1. By using memory router and 2. by mocking your match object

    import { MemoryRouter } from 'react-router-dom';
    
    test('<Movie />', () => {
      render(
        <MemoryRouter>
          <Movie />
        </MemoryRouter>
      );
      // assert here
    });

    Clearing your Mock calls

    If running multiple tests in a suite and spying on mocked function calls, you may need to clean up the call counter after each test. Here's an example of resetting a console.error spy:

    
    afterEach(() => {
      cleanup();
      console.error.mockClear();
    });
    
    console.error = jest.fn();

    cleanup(); is doing the same for React components - otherwise, state and props will linger from test to test.