9 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 or class produce the expected output or response.

    • These tests typically apply to self-contained business logic, 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 given function or class and focus on isolating the tested function or class.

  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 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 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 component class, except for things that require CSS for styling. jsdom does not process CSS, so avoid using these tests to validate any CSS. It is also not suitable for testing the custom element rendering of a component in your app, as the browser environment from jsdom is a simulation rather than a real browser.

The Preact Testing Library provides a set of utility functions that make it easy to write tests that assert the behavior of Preact component classes without relying on their implementation details. It promotes a UI-centric approach to testing: component classes 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 Oracle JET CLI’s add testing command to add and configure the dependencies and libraries, including Jest and the Preact Testing Library, to test Oracle JET virtual DOM apps and VComponent components.

After you run this command in your app, you can create and run tests for your app using Jest and the Preact Testing Library. The add testing command adds the test-config directory to your app's root directory. This directory contains two files that are required to set up testing: jest.config.js and testSetup.ts. The testSetup.ts file imports runtime support for compiled and transpiled async functions, while jest.config.js is Jest's configuration file.

The extension for files containing tests, known as "spec files," should be .spec.tsx so that Oracle JET tooling and your Jest testing configuration recognize them. If spec files are missing from a component, the add testing command creates them. Spec files are located within a __tests__ directory inside the components directory, such as ./src/components/<component-name>/__tests__. This directory holds the test files you write for your component and, by default, the add testing command creates a spec file with the following file name pattern: <component-name>.spec.tsx.

Note:

If you create a new component or JET pack from the command line after running the add testing command on your project, then the __tests__ directory and are spec file are created default when you create the component.

In the following steps, we demonstrate how to set up an Oracle JET app for testing, and how to run a unit test and a component test.

  1. Open a terminal window in a directory of your choice to create a new Oracle JET app using the basic template:

    npx @oracle/ojet-cli create vdomTestApp --template=basic --vdom
  2. Navigate to the newly created app's root directory and create a new VComponent component:

    cd vdomTestApp
    npx ojet create component hello-world
  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>
      );
    };
    

    Note:

    We render the HelloWorld component using the Preact component class syntax (import {HelloWorld} . . . and <HelloWorld />) because Jest cannot test the custom element alternative (import "hello-world/loader" . . . and <hello-world></hello-world>).
  4. To run the app in your browser and confirm that the app renders the newly-created HelloWorld component, enter the following command in the terminal window for the app’s root directory:

    npx ojet serve

    This is also a required step for testing. Before you can test components, you must build the component using the Oracle JET CLI.


    The HelloWorld component rendered in the running app

    Note:

    The HelloWorld component passes "Hello from hello world" to the app through its message property. This confirms that the app and component function as expected.
  5. In the terminal window of your app's root directory, add the testing libraries:

    npx ojet add testing

    This adds the test-config directory to the root directory of your app and the __tests__ directory to the component directory (./src/components/hello-world ). The __tests__ directory contains a spec file (hello-world.spec.tsx) 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 tests that the component renders in the app.

    ./vdomTest/test-config
    jest.config.js
    testSetup.ts

    The add testing command also updates the package.json file with the testing dependencies, 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. To provide a function for a unit test to test, open the ./VDOMTestApp/src/components/hello-world/hello-world.tsx file for the HelloWorld component that we created previously and add the sum function at the end of the file:
    . . .
    >> = registerCustomElement("hello-world", HelloWorldImpl);
    
    // function for doc example
    export const sum = (a: number, b: number) => {
      return a + b;
    };
  7. To write a unit test for this function, open the ./src/components/hello-world/__tests__/hello-world.spec.tsx file and add the following entries:

    import { render } from "@testing-library/preact";
    import { HelloWorld } from "hello-world/hello-world";
    import { sum } from "hello-world/hello-world";
    
    describe("Test description", () => {
      test("Your test title", async () => {
        const content = render(
          <div data-oj-binding-provider="preact">
            <HelloWorld />
          </div>
        );
        expect(true).not.toBeUndefined;
      });
    
      it("The sum is 10", () => {
        expect(sum(6, 4)).toBe(10);
      });
    });
    
  8. In the terminal window of your app’s root directory, enter the following commands to build the Oracle JET app and execute the tests:

    npx ojet build
    npm run test

    The terminal window displays the test results:

    vdomTest
    $ npm run test
    
    > vdomTestJET15@1.0.0 test
    > jest -c test-config/jest.config.js
    vdomTest
    $ npm run test
    
    > vdomTestJET15@1.0.0 test
    > jest -c test-config/jest.config.js
    
    PASS  src/components/hello-world/__tests__/hello-world.spec.tsx
      Test description
        √ Your test title (5 ms)                                                                                                                                    
        √ The sum is 10 (1 ms)                                                                                                                                      
                                                                                                                                                                    
    Test Suites: 1 passed, 1 total                                                                                                                                  
    Tests:       2 passed, 2 total                                                                                                                                  
    Snapshots:   0 total
    Time:        1.966 s, estimated 3 s
    Ran all test suites.
    
    
    PASS  src/components/hello-world/__tests__/hello-world.spec.tsx
      Test description
        √ Your test title (5 ms)                                                                                                                                    
        √ The sum is 10 (1 ms)                                                                                                                                      
                                                                                                                                                                    
    Test Suites: 1 passed, 1 total                                                                                                                                  
    Tests:       2 passed, 2 total                                                                                                                                  
    Snapshots:   0 total
    Time:        1.966 s, estimated 3 s
    Ran all test suites.

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.

One other way to improve the debugging experience is to set the generateSourceMaps in the oraclejetconfig.json file to true from its default value of false. When true, the Oracle JET CLI configures Terser and RequireJS packages to generate source map files when you build your Oracle JET app.

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"
      }
    },