7 Test and Debug Oracle JET Apps

Test and debug Oracle JET web apps using a recommended set of testing and debugging tools for client-side apps.

Test Oracle JET Apps

Tests help you build complex Oracle JET apps quickly and reliably by preventing regressions and encouraging you to create apps that are composed of testable functions, modules, classes, and components.

We recommend that you write tests as early as possible in your app’s development cycle. The longer that you delay testing, the more dependencies the app is likely to have, and the more difficult it will be to begin testing.

Testing Types

There are three main testing types that you should consider when testing Oracle JET apps.

  1. Unit Testing
    • Unit testing checks that all inputs to a given function, class, or component are producing the expected output or response.

    • These tests are typically applied to self-contained business logic, components, classes, modules, or functions that do not involve UI rendering, network requests, or other environmental concerns.

      Note that REST service APIs should be tested independently.

    • Unit tests are aware of the implementation details and dependencies of a component and focus on isolating the tested component.

  2. Component Testing
    • Component testing checks that individual components can be interacted with and behave as expected. These tests import more code than unit tests, are more complex, and require more time to execute.

    • Component tests should catch issues related to your component's properties, events, the slots that it provides, styles, classes, lifecycle hooks, and more.

    • These tests are unaware of the implementation details of a component; they mock up as little as possible in order to test the integration of your component and the entire system.

      You should not mock up child components in component tests but instead check the interactions between your component and its children with a test that interacts with the components as a user would (for example, by clicking on an element).

  3. End-to-End Testing
    • End-to-end testing, which often involves setting up a database or other backend service, checks features that span multiple pages and make real network requests against a production-built JET app.

End-to-end testing is meant to test the functionality of an entire app, not just its individual components. Therefore, use unit tests and component tests when testing specific components of your Oracle JET apps.

Unit Testing

Unit testing should be the first and most comprehensive form of testing that you perform.

The purpose of unit testing is to ensure that each unit of software code is coded correctly, works as expected, and returns the expected outputs for all relevant inputs. A unit can be a function, method, module, object, or other entity in an app’s source code.

Unit tests are small, efficient tests created to execute and verify the lowest-level of code and to test those individual entities in isolation. By isolating functionality, we remove external dependencies that aren't relevant to the unit being tested and increase the visibility into the source of failures.

Unit tests should interact with the component's public application programming interface (API) and pass the API as many different combinations of test data as necessary to exercise as much of the component's code paths as possible. This includes testing the component's properties, events, methods, and slots.

Unit tests that you create should adhere to the following principles:

  • Easy to write: Unit testing should be your main testing focus; therefore, tests should typically be easy to write because many will be written. The standard testing technology stack combined with recommended development environments ensures that the tests are easily and quickly written.
  • Readable: The intent of each test should be clearly documented, not just in comments, but the code should also allow for easy interpretation of what its purpose is. Keeping tests readable is important should someone need to debug when a failure occurs.
  • Reliable: Tests should consistently pass when no bugs are introduced into the component code and only fail when there are true bugs or new, unimplemented behaviors. The tests should also execute reliably regardless of the order in which they’re run.
  • Fast: Tests should be able to execute quickly and report issues immediately to the developer. If a test runs slowly, it could be a sign that it is dependent upon an external system or interacting with an external system.
  • Discrete: Tests should exercise the smallest unit of work possible, not only to ensure that all units are properly verified but also to aid in the detection of bugs when failures occur. In each unit test, individual test cases should independently target a single attribute of the code to be verified.
  • Independent: Above all else, unit tests should be independent of one another, free of external dependencies, and be able to run consistently irrespective of the environment in which they’re executed.

To shield unit tests from external changes that may affect their outcomes, unit tests focus solely on verifying code that is wholly owned by the component and avoid verifying the behaviors of anything external to that component. When external dependencies are needed, consider using mocks to stand in their place.

Component Testing

The purpose of component testing is to establish that an individual component behaves and can be interacted with according to its specifications. In addition to verifying that your component accepts the correct inputs and produces the right outputs, component tests also include checking for issues related to your component's properties, events, slots, styles, classes, lifecycle hooks, and so on.

A component is made up of many units of code, therefore component testing is more complex and takes longer to conduct than unit testing. However, it is still very necessary; the individual units within your component may work on their own, but issues can occur when you use them together.

Component testing is a form of closed-box testing, meaning that the test evaluates the behavior of the program without considering the details of the underlying code. You should begin testing a component in its entirety immediately after development, though the tested component may in part depend on other components that have not yet been developed. Depending on the development lifecycle model, component testing can be done in isolation from other components in the system, in order to prevent external influences.

If the components that your component depends on have not yet been developed, then use dummy objects instead of the real components. These dummy objects are the stub (called function) and the controller (called function).

Depending on the depth of the test level, there are two types of component tests: small component tests and large component tests.

When component testing is done in isolation from other components, it is called "small component testing." Small component tests do not consider the component's integration with other components.

When component testing is performed without isolating the component from other components, it is called "large component testing", or "component testing" in general. These tests are done when there is a dependency on the flow of functionality of the components, and therefore we cannot isolate them.

End-to-End Testing

End-to-end testing is a method of evaluating a software product by examining its behavior from start to finish. This approach verifies that the app operates as intended and confirms that all integrated components function correctly in relation to one another. Additionally, end-to-end testing defines the system dependencies of the product to ensure optimal performance.

The primary goal of end-to-end testing is to replicate the end-user experience by simulating real-world scenarios and evaluating the system and its components for proper integration and data consistency. This approach allows for the validation of the system's performance from the perspective of the user.

End-to-end testing is a widely adopted and reliable technique that provides the following advantages.
  • Comprehensive test coverage
  • Assurance of app's accuracy
  • Faster time to market
  • Reduced costs
  • Identification of bugs
Modern software systems are increasingly interconnected, with various subsystems that can cause adverse effects throughout the entire system if they fail. End-to-end testing can help prevent these risks by:
  • Verifying the system's flow
  • Increasing the coverage of testing areas
  • Identifying issues related to subsystems
End-to-end testing is beneficial for a variety of stakeholders:
  • Developers appreciate end-to-end testing as it allows them to offload testing responsibilities.
  • Testers find it useful as it enables them to write tests that simulate real-world scenarios and avoid potential problems.
  • Managers benefit from end-to-end testing as it allows them to understand the impact of a failing test on the end-user.
The end-to-end testing process comprises four stages:
  1. Test Planning: Outlining key tasks, schedules, and resources required
  2. Test Design: Creating test specifications, identifying test cases, assessing risks, analyzing usage, and scheduling tests
  3. Test Execution: Carrying out the test cases and documenting the results
  4. Results Analysis: Reviewing the test results, evaluating the testing process, and conducting further testing as required
There are two approaches to end-to-end testing:
  • Horizontal Testing: This method involves testing across multiple apps and is often used in a single ERP (Enterprise Resource Planning) system.
  • Vertical Testing: This approach involves testing in layers, where tests are conducted in a sequential, hierarchical order. This method is used to test critical components of a complex computing system and does not typically involve users or interfaces.

End-to-end testing is typically performed on finished products and systems, with each review serving as a test of the completed system. If the system does not produce the expected output or if a problem is detected, a second test will be conducted. In this case, the team will need to record and analyze the data to determine the source of the issue, fix it, and retest.

While testing your app end-to-end, consider the following metrics:
  • Test Case Preparation Status: This metric is used to track the progress of test cases that are currently being prepared in comparison to the planned test cases.
  • Test Progress Tracking: Regular monitoring of test progress on a weekly basis to provide updates on test completion percentage and the status of passed/failed, executed/unexecuted, and valid/invalid test cases.
  • Defects Status and Details: Provides a weekly percentage of open and closed defects and a breakdown of defects by severity and priority.
  • Environment Availability: Information on the number of operational hours and hours scheduled for testing each day.

About the Oracle JET Testing Technology Stack

The recommended stack for testing Oracle JET apps includes Jest and the Preact Testing Library.

Jest is a popular testing framework for JavaScript/Typescript that comes with its own test runner and assertion functions. It supports code coverage and snapshot testing, is simple to create mocks with, and runs tests in parallel, which ensures that they remain isolated.

Jest runs in NodeJS using jsdom as a simulated browser environment. These tests run very quickly because the environment doesn't need to render anything; it is a lightweight, in-memory implementation of the DOM that runs headless. Jest tests are suitable for verifying almost every aspect of your components, except for things that require CSS for styling. jsdom does not process CSS, so avoid using these tests to validate any CSS.

The Preact Testing Library provides a set of utility functions that make it easy to write tests that assert the behavior of Preact components without relying on their implementation details. It promotes a UI-centric approach to testing: components are allowed to go through their full rendering lifecycles, and the library provides query functions to locate elements within the DOM and a user-event simulation library to interact with them.

The functions in the Preact Testing Library work with the actual DOM elements that are rendered by Preact, rather than with the virtual DOM, so tests will resemble how a user interacts with the app and finds elements on the page.

For UI automation testing, we recommend using Selenium WebDriver in conjunction with the Oracle® JavaScript Extension Toolkit (Oracle JET) WebDriver.

Configure Oracle JET Apps for Testing

Use the ojet add testing CLI command to add testing capability to your Oracle JET app by setting up the framework, dependencies, and libraries required for testing for JET components, including Jest and the Preact Testing Library.

The ojet add testing Command

Run the command from a terminal window in your app's root directory. After it configures your app's testing environment, you can proceed to create and run tests on your app using Jest and the Preact Testing Library.

The configuration performed by the ojet add testing command creates some directories, dependencies, and files within your app that are essential to testing.

For instance, the configuration adds the test-config folder to your app's root directory. It contains two files that are required for testing: jest.config.js and testSetup.ts.

The file testSetup.ts imports runtime support for compiled and transpiled async functions, while jest.config.js is Jest's configuration file.

Additionally, existing components are checked for test files. The extension for files containing tests, known as "spec files," should be .spec.tsx so that tooling and your Jest testing configuration recognize them. If spec files are missing from a component, then they are injected. Spec files are located within a __tests__ folder inside the components folder, such as src/components/<component-name>/__tests__. This folder holds the test files you write for your component and, by default, is created with a spec file: <component-name>.spec.tsx.

Note:

If you create a new component or pack from the command line after running the add testing command on your project, then the __tests__ folder and its spec file are injected by default.

Testing Demo

Here we will demonstrate how to use the ojet add testing command to configure a testing environment for an Oracle JET app. After testing is enabled, we will run a unit test and a component test on the sample app.

  1. First, create a new Oracle JET app using the basic template. Open a terminal window in your working directory and run the command npx @oracle/ojet-cli create vdomTestApp --template=basic --vdom.
  2. Enter cd vdomTestApp to navigate to the app's root directory, then enter the command npx ojet create component hello-world to create a new component.
  3. Open the ./VDOMTestApp/src/components/content/index.tsx file to import and display the newly-created HelloWorld component:
    import { HelloWorld } from "hello-world/loader"
    
    export function Content() {
      return (
        <div class="oj-web-applayout-max-width oj-web-applayout-content">
          <HelloWorld />
        </div>
      );
    };
  4. Enter the npx ojet serve command to run the app in your browser and confirm that the app runs and the new component renders. This is also a required step for testing, as before the components can be tested, they must first be built by the Oracle JET CLI.


    The HelloWorld component rendered in the running app

    Note:

    The text "Hello from hello world" visible in the running app is passed into your app's content through the HelloWorld component's message property, confirming that the component is working.
  5. Run the npx ojet add testing command from a terminal window in your app's root directory.

    In addition to configuring your testing environment, in your app's directory structure, you can see that the test-config folder was added to the root directory and the __tests__ folder was added to the /src/components/hello-world directory.

    Inside the __tests__ folder is the spec file hello-world.spec.tsx, created by default with a component test for your HelloWorld component that verifies that it renders. Jest provides the test case and assertions (describe(), test(), and expect(true).not.toBeUndefined;), whereas the render() function from the Preact Testing Library is used to render the component in the app.

    If you look in the package.json file, you can see the testing dependencies that were added, such as the Jest preset that allows Oracle JET Web Elements to be used in Jest tests, and two convenience scripts, test and test:debug.

  6. In addition to the component test, we will run a unit test. First, we must provide a function to be tested.

    Open the ./VDOMTestApp/src/components/hello-world/hello-world.tsx file in your code editor and, at the bottom of the file, insert the following function that returns the sum of two numbers. Save the file.

    export const sum = (a: number, b: number) => {
      return a + b;
    }
  7. Open the hello-world.spec.tsx file and, at the top of the file, add an import statement for the sum function: import { sum } from 'hello-world/hello-world'.
  8. At the bottom of the hello-world.spec.tsx file, add the following unit test for the sum function. Save the file.
    it('The sum is 10', () => {
      expect(sum(6, 4)).toBe(10)
    })
  9. Build your app's project source by running the following command from the ./VDOMTestApp/ directory in a terminal window

    npx ojet build

  10. Run the tests. Enter npm run test in the command line and observe the results in the terminal window.The results of running the tests on the HelloWorld component

Debug Oracle JET Apps

Since Oracle JET web apps are client-side HTML5 apps written in JavaScript or Typescript, you can use your favorite browser's debugging facilities.

Debug Web Apps

Use your source code editor and browser's developer tools to debug your Oracle JET app.

Developer tools for widely used browsers like Chrome, Edge, and Firefox provide a range of features that assist you in inspecting and debugging your Oracle JET app as it runs in the browser. Read more about the usage of these developer tools in the documentation for your browser.

By default, the ojet build and ojet serve commands use debug versions of the Oracle JET libraries. If you build or serve your Oracle JET app in release mode (by appending the --release parameter to the ojet build or ojet serve command), your app uses minified versions of the Oracle JET libraries. If you choose to debug an Oracle JET app that you built in release mode, you can use the --optimize=none parameter to make the minified output more readable by preserving line breaks and white space:

ojet build --release --optimize=none
ojet serve --release --optimize=none

Note that browser developer tools offer the option to "pretty print" minified source files to make them more readable, if you choose not to use the --optimize=none parameter.

You may also be able to install browser extensions that further assist you in debugging your app.

Finally, if you use a source code editor, such as Visual Studio Code, familiarize yourself with the debugging tools that it provides to assist you as develop and debug your Oracle JET app.

Use Preact Developer Tools

You can install a Preact browser extension to provide additional debugging tools in your browser’s developer tools when you debug your virtual DOM app.

Preact provides download links for the various browser extensions at https://preactjs.github.io/preact-devtools/.

Once you have installed the extension for your browser, you need to include an import statement for preact/debug as the first line in your app's appRootDir/src/index.ts file:
import 'preact/debug';
import './components/app';

Oracle JET takes care of including this import when your virtual DOM app is built or served in debug mode (the default option for ojet build and ojet serve) by including the following injector token when you create the virtual DOM app:

// injector:preactDebugImport
// endinjector
import './components/app';

As a result, you do not need to include the import 'preact/debug' statement when you build or serve your app in debug mode or remove it when you build or serve for release, as Oracle JET’s injector token ensures that the import statement is only included in debug mode.

When you serve your virtual DOM app in debug mode (the default option for ojet serve), you’ll see an extra tab, Preact, in your browser’s developer tools. In the following image, you see the Preact tab in the Chrome browser’s DevTools.

You can view the hierarchy of the components, select and inspect components, and perform other actions that assist you in debugging issues with your virtual DOM app.

The surrounding text describes the image

When you build or serve the virtual DOM app in release mode, using the --release argument, Oracle JET does not import the Preact DevTools. Remove the token if you do not want to use the Preact DevTools in debug mode.

One other thing to note is that Oracle JET includes the following entries in your app's appRootDir/src/path_mapping.json file when it creates your app. You need these entries to be able to use the Preact extension discussed here.

. . . 
"preact/debug": {
      "cdn": "3rdparty",
      "cwd": "node_modules/preact/debug/dist",
      "debug": {
        "src": [
          "debug.umd.js",
          "debug.umd.js.map"
        ],
        "path": "libs/preact/debug/dist/debug.umd.js",
        "cdnPath": "preact/debug/dist/debug.umd"
      },
      "release": {
        "src": [
          "debug.umd.js",
          "debug.umd.js.map"
        ],
        "path": "libs/preact/debug/dist/debug.umd.js",
        "cdnPath": "preact/debug/dist/debug.umd"
      }
    },
    "preact/devtools": {
      "cdn": "3rdparty",
      "cwd": "node_modules/preact/devtools/dist",
      "debug": {
        "src": [
          "devtools.umd.js",
          "devtools.umd.js.map"
        ],
        "path": "libs/preact/devtools/dist/devtools.umd.js",
        "cdnPath": "preact/devtools/dist/devtools.umd"
      },
      "release": {
        "src": [
          "devtools.umd.js",
          "devtools.umd.js.map"
        ],
        "path": "libs/preact/devtools/dist/devtools.umd.js",
        "cdnPath": "preact/devtools/dist/devtools.umd"
      }
    },