• Labs icon Lab
  • Core Tech
Labs

Guided: Testing and Advanced Features in Next.js 14

In this Code Lab, you will enhance a weather application with unit tests, Incremental Static Regeneration (ISR) for updated content, and advanced API routes for user interactions. You will learn how to write unit tests for components, functions, and user interactions using Jest and React Testing Library. You will also learn how to implement API routes to fetch weather data and update the application. Finally, you will implement ISR to update the weather data dynamically without rebuilding the entire application. By the end of this Code Lab, you will have a better understanding of testing, API routes, and ISR in Next.js.

Labs

Path Info

Level
Clock icon Beginner
Duration
Clock icon 45m
Published
Clock icon Dec 17, 2024

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction

    Introduction

    Welcome to this Guided Next.js 14 Testing and Advanced Features Code Lab! In this Code Lab, you will enhance a weather application by adding unit tests, Incremental Static Regeneration (ISR) for updated content, and advanced API routes for user interactions. You will learn how to:

    • Write unit tests for UI components and logic functions using Jest and React Testing Library.
    • Implement API routes to fetch weather data and dynamically update the application.
    • Use ISR to refresh weather data dynamically without requiring a full rebuild.

    About the Code Lab

    Tasks can be validated by clicking on the checkboxes next to them. If you are stuck or would like to compare solutions, a solution directory has been provided for you as well. After attempting the task, you can choose to "show" the task solution which includes a hint and a link to the solution file that you can double click. ## Weather App Overview

    The weather application you'll be working on is a simple Next.js app that displays the current weather for specific locations. It contains a few bugs that you'll address as part of the lab. The application includes the following features:

    • Home Page: Displays the current weather in multiple locations and includes a search bar.
    • Weather Details Page: Provides detailed weather information for a selected location, including a 5-day extended forecast.
    • Temperature Unit Toggle: Allows users to switch between Celsius and Fahrenheit globally.
    • Favorites Feature: Enables users to save their favorite locations and view them in a separate list.

    Application Structure

    The application is organized as follows:

    • app/: Contains the Next.js routes.
    • components/: Contains React components, along with CSS modules and test files.
    • lib/: Holds utility functions and constants used throughout the application.
    • data/: Includes mocked weather data for various locations.

    Note: This application is a simplified version for the purposes of this Code Lab. Weather data is hardcoded and does not update in real-time. You will implement API routes to simulate fetching random weather data and updating the application. In a real-world scenario, you might integrate with a third-party weather API, along with handling features like authentication and storage.

    Take a moment to explore the weather application and familiarize yourself with its existing pages and components. Don't worry too much about the details at this stage—you'll delve deeper as you progress through the lab.

    To run the application, execute the following command in the Terminal tab:

    npm run dev
    

    Then, open the Web Browser tab and navigate to localhost:3000, or use the following link to view the application in your own browser: {{localhost:3000}}

  2. Challenge

    Step 1a: Writing Unit Tests

    Step 1: Writing Unit Tests

    In this step, you will write unit tests for the weather application to ensure it works as expected and fix existing bugs. You'll use Jest and React Testing Library to test components, functions, and user interactions. ## Find a Bug

    This application was developed by another team, and it hasn't been tested thoroughly. Subsequent fixes and features added by different developers introduced some bugs. A particularly persistent issue has been reported regarding the temperature conversion functionality.

    In software development, modifying existing code often risks introducing new bugs or regressions, especially if the code lacks automated tests. Tests help catch these bugs early, enable confident refactoring, and even improve design, documentation, and understanding of the code.

    Currently, the application has a bug with the Celsius-to-Fahrenheit conversion. When switching temperature units globally, the displayed temperature values are incorrect. This issue stems from the logic being spread across multiple components and functions.

    You'll start by writing a test to identify and reproduce this bug. Writing a test for an existing bug ensures that you can verify when it is fixed.

    Tip: "Never trust a test you haven't seen fail." Always write a failing test first to confirm that it accurately reflects the issue. Only then should you implement the fix to ensure the test is meaningful.

    To reproduce this issue, run the application, navigate to the home page, and toggle the temperature setting to Fahrenheit. You will notice that the Fahrenheit values do not correspond to the correct conversion from Celsius.

    You'll now write a test to reproduce the problem and guide your fix. ## Test-Driven Development (TDD)

    Test-Driven Development (TDD) is a process that emphasizes short, iterative development cycles:

    1. Write an automated test that defines a desired improvement or new feature (the test should initially fail).
    2. Implement the minimal code necessary to pass the test.
    3. Refactor the code to meet acceptable standards while ensuring the test continues to pass.

    This is often referred to as the Red-Green-Refactor cycle:

    • Red: Write a failing test.
    • Green: Write the code to pass the test.
    • Refactor: Clean up the code while maintaining the functionality.

    While pure TDD is typically used during new development, you'll adapt this methodology to debug and enhance the existing application. This will provide you with a taste of the TDD experience as you navigate through a few Red-Green-Refactor cycles.

    Note: strictly speaking, the iterations should be very short, especially when you are new to TDD. For example, you could write a test that imports an undefined component, and it will count as a "Red" step. Then, you would write only the component definition and the test would pass. From there you would expand the test to make an assertion about the component, and then write the code to make the test pass. In this way, you go back between code and tests dozens of times before arriving to the definitive solution. To keep this lab focused, you will skip some of these steps in the tasks, but I encourage you to try them on your own, at least for the first few tests. ## Write a Test for the Temperature Conversion

    You'll use Jest and React Testing Library to write a test for the temperature conversion logic. Jest is a JavaScript testing framework for writing and running tests, while React Testing Library helps test React components in a way that mimics user interactions without requiring a browser.

    Isolating the Conversion Logic

    Ideally, you would isolate the temperature conversion logic and test it separately from the components. However, the logic is currently embedded in two components: CityCard and CityDetail.

    You'll begin with the CityCard component, which displays the current weather for a city, including its temperature in either Celsius or Fahrenheit, depending on the global setting.

  3. Challenge

    Step 1b: Refactoring

    Refactoring the Temperature Conversion Logic

    To improve maintainability, you'll move the temperature conversion logic to a separate function. This will allow you to:

    1. Reuse the logic across components.
    2. Test the logic independently of React components.
    3. Reduce code duplication.

    Refactoring the CityDetail Component

    The CityDetail component displays detailed weather information for a specific city, including an extended 5-day forecast. Like the CityCard component, it displays temperatures in either Celsius or Fahrenheit, based on the global setting. However, the temperature conversion logic is currently embedded in the component, making it harder to maintain and reuse.

    Identifying the Bug

    If you navigate to the weather details page, you’ll notice:

    1. The current temperature appears to convert correctly between Celsius and Fahrenheit (likely due to an earlier fix specific to this component, but the code duplication and lack of tests made them miss the CityCard component you just fixed).
    2. The forecast table shows incorrect values: the conversion bug is still present here, and additionally, all temperatures match the current temperature, which is incorrect as the forecast should show different values for each day.

    This bug likely stems from reusing the current temperature (city.temperatureC) instead of the individual forecast temperatures (forecast.temperatureC). You will address this by first adding a test to reproduce the bug.

  4. Challenge

    Step 1c: Further Refactoring

    Opportunities for Further Refactoring

    While the logic is now centralized, you can further improve the CityCard and CityDetail components:

    1. Remove Ternary Duplication: Encapsulate the logic for selecting and converting the temperature into a utility function, reducing repetition across components.
    2. Separate Concerns: Extract the temperature display logic into a reusable TemperatureDisplay component. This allows:
      • Reusing the component across the app.
      • Reducing reliance on the global store within large components.
      • Transforming CityCard and CityDetail into server components, improving performance and SEO. ## Conclusion

    Through this refactor, you achieved:

    1. Simplification: Extracting shared logic reduces duplication and makes components easier to maintain.
    2. Modularity: The TemperatureDisplay component is reusable across the app.
    3. Performance Improvements: By reducing client-side rendering, you’ve improved SEO and load times.

    Tip: When writing code, it’s rare to arrive at the best solution on the first try. That’s why an iterative approach is key: start with the simplest code that works (even if it’s not perfect), and then refactor to improve readability, reusability, and performance. Refactoring involves extracting common logic, reorganizing code, and making optimizations – all without changing functionality. To refactor confidently, you need tests. Tests act as a safety net, ensuring that your changes don’t break existing functionality. They also allow you to focus on improving your code without second-guessing yourself. If you’re new to writing tests, it might feel like extra work at first or seem repetitive. However, the effort pays off in the long run.

  5. Challenge

    Step 2: Implementing API Routes

    Step 2: Implementing API Routes

    In this section, you'll move the app from hardcoded data to using Next.js API Routes to fetch dynamic data. Next.js API Routes allow you to create server-side endpoints within your application. These routes can be used to fetch data or handle actions, just like an API you’d create in a backend server.

    For this lab, you’ll mimic dynamic behavior by generating random weather data through API routes. While these routes will still use hardcoded base data, you can imagine integrating them with a real database or a third-party API in a full application.

    Using the App Router in Next.js, API routes are defined in the /app folder. In the same way that page.tsx files are rendered based on the filesystem route, a route.ts file can be used to reply to API requests. In this case, you'll place them in the app/api folder. ## Observing the Change

    Run the app and notice how temperatures now appear randomized. However, if you refresh the page multiple times, the temperatures don’t update, even though you verified that the API endpoint does return a random value with each request. Why does this happen?

    This happens because Next.js uses Server-Side Rendering (SSR), and the page is generated statically at build time unless configured otherwise. Next.js only makes the API request once, generates the static page, and then serves that same content on every request. In the next step, you’ll explore how to manage this behavior using caching and revalidation. Now you would need to add tests for the LikeButton component. You will not do this in this lab, but you can try it on your own. For this, you need to mock the window.fetch function, and then test that the component makes the correct requests when clicked.

  6. Challenge

    Step 3: Incremental Static Regeneration (ISR)

    Step 3: Incremental Static Regeneration (ISR)

    In the previous step, you observed that although your API generates randomized data, refreshing the page does not reflect these changes. This behavior occurs because Next.js pages are static by default. You will explore how caching and revalidation work in Next.js.

    Try accessing http://localhost:3000/ and note that the temperatures don't change with each refresh, while the API endpoint http://localhost:3000/api/cities does return random temperatures. Double check this via the Web Browser (remember to select to see the white-on-white text), or via the Terminal using curl localhost:3000/api/cities.

    Static vs. Dynamic Data Fetching

    Next.js supports several fetching strategies:

    1. Static Data: Data is fetched at build time and remains unchanged.
    2. Dynamic Data: Data is fetched at request time, ensuring the latest updates.
    3. Incremental Static Regeneration (ISR): Combines the benefits of static and dynamic fetching by periodically revalidating and updating static content.
  7. Challenge

    Conclusion

    Conclusion

    In this Code Lab, you learned how to:

    • Write tests using Jest and React Testing Library. You followed the Test-Driven Development (TDD) cycle to write tests for both existing and new features, comprising UI components and utility functions.
    • Refactor code to improve maintainability and reusability. You extracted shared logic into utility functions and components, reducing duplication and making the code easier to test and maintain. Tests acted as a safety net, ensuring that the refactored code didn’t break existing functionality.
    • Implement API routes in Next.js to fetch dynamic data. You created server-side endpoints to simulate dynamic behavior, allowing the app to fetch data from an API instead of using hardcoded values.
    • Manage data fetching strategies in Next.js. You explored different data fetching strategies, including static, dynamic, and Incremental Static Regeneration (ISR), to balance performance and freshness of data. You also explored the differences between server-side rendering and client-side rendering.

    From here, you can explore more advanced topics, such as:

    • Authentication and Authorization
    • State Management with React Context, Redux or Zustand
    • Serverless Functions
    • Integration Tests with Cypress or Playwright

    Thank you for following along!

Julian is a Backend Engineer working with Java, Python, and distributed cloud systems. He has worked in Amazon, Google, and various startups, and focuses on first principles over tools.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.