- Lab
- Core Tech

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.

Path Info
Table of Contents
-
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}} -
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:
- Write an automated test that defines a desired improvement or new feature (the test should initially fail).
- Implement the minimal code necessary to pass the test.
- 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
andCityDetail
.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. -
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:
- Reuse the logic across components.
- Test the logic independently of React components.
- Reduce code duplication.
Refactoring the
CityDetail
ComponentThe
CityDetail
component displays detailed weather information for a specific city, including an extended 5-day forecast. Like theCityCard
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:
- 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). - 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. -
Challenge
Step 1c: Further Refactoring
Opportunities for Further Refactoring
While the logic is now centralized, you can further improve the
CityCard
andCityDetail
components:- Remove Ternary Duplication: Encapsulate the logic for selecting and converting the temperature into a utility function, reducing repetition across components.
- 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
andCityDetail
into server components, improving performance and SEO. ## Conclusion
Through this refactor, you achieved:
- Simplification: Extracting shared logic reduces duplication and makes components easier to maintain.
- Modularity: The
TemperatureDisplay
component is reusable across the app. - 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.
-
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 thatpage.tsx
files are rendered based on the filesystem route, aroute.ts
file can be used to reply to API requests. In this case, you'll place them in theapp/api
folder. ## Observing the ChangeRun 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 thewindow.fetch
function, and then test that the component makes the correct requests when clicked. -
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 endpointhttp://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 usingcurl localhost:3000/api/cities
.Static vs. Dynamic Data Fetching
Next.js supports several fetching strategies:
- Static Data: Data is fetched at build time and remains unchanged.
- Dynamic Data: Data is fetched at request time, ensuring the latest updates.
- Incremental Static Regeneration (ISR): Combines the benefits of static and dynamic fetching by periodically revalidating and updating static content.
-
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!
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.