« return to the manuals

Introduction to LaxarJS Mocks

Before going into details, we will introduce the testing framework by showing an example and use that to describe the individual steps usually found in a widget test. Coming from LaxarJS 1.x, the migration guide may help. If you are already familiar with the basics, you may want to browse the API docs instead.

An Example Test

The following is a simple test for a plain widget. Consult your adapter's documentation to find any additional information for your integration technology.

Below the example, each of the enumerated points is described in more detail. Note that some of the steps are optional, depending on your testing needs.

// 1. Module Definition, Imports
import * as axMocks from 'laxar-mocks';

describe( 'An example-widget', () => {

   // 2. Testbed Setup, creates axMocks.widget for the widget under test
   beforeEach( axMocks.setupForWidget() );

   beforeEach( () => {
      // 3. Widget Configuration (as needed by the widget)
      axMocks.widget.configure( {
         example: {
            resource: 'exampleResource',
            action: 'exampleAction'
         }
      } );

      // 4. Optional: Configuring and/or Replacing Widget Services
      axMocks.widget.whenServicesAvailable( services => {
         services.axFlowService.constructAbsoluteUrl
            .and.callFake( target => target === 'next' ? '/step2' : '/default' );
         services.axId = suffix => `ABC-${suffix}`;
      } );
   } );

   // 5. Loading the Widget, and instantiating the controller
   beforeEach( axMocks.widget.load );

   // 6.  Optional: Simulating Startup Events
   beforeEach( axMocks.triggerStartupEvents );

   // 7. Optional: Rendering the Widget DOM
   let widgetDom;
   beforeEach( () => { widgetDom = axMocks.widget.render(); } );

   // 8. Tests
   it( 'subscribes to didReplace events for the example resource', () => {
      expect( axMocks.widget.axEventBus.subscribe )
         .toHaveBeenCalledWith( 'didReplace.exampleResource', jasmine.any( Function ) );
   } );

   it( 'uses the flow service to generate a "next" link', () => {
      expect( axMocks.widget.axFlowService.constructAbsoluteUrl )
         .toHaveBeenCalledWith( 'next', {} );
   } );

   // 9. Optional: DOM-Tests
   it( 'renders a link refering to the "next" target', () => {
      expect( widgetDom.querySelector( 'a' ).href ).toEqual( '/step2' );
   } );

   it( 'renders an input with matching label', () => {
      expect( widgetDom.querySelector( 'input' ).id ).toEqual( 'ABC-myInput' );
      expect( widgetDom.querySelector( 'label' ).for ).toEqual( 'ABC-myInput' );
   } );

   // 10. Testbed Tear-Down
   afterEach( axMocks.tearDown );

} );

The individual steps are explained below in more detail.

1. Module Definition, Imports

For simplicity, this test is written as an ES2015 module, but other styles (CommonJS, AMD) should work just fine. The LaxarJS spec-loader takes care of loading the widget itself and its dependencies, such as controls.

2. Testbed Setup

LaxarJS mocks has already been provisioned with information on the widget's assets, so no arguments need to be passed here To spare the user from handling of asynchronous tasks in the Jasmine environment, axMocks.setupForWidget() returns an asynchronous function suitable for use with beforeEach, so the parentheses must not be removed.

3. Widget Configuration

Next, we configure the features of the widget instance. The same information that you would configure as features within a page when developing an application can be passed to the method axMocks.widget.configure. For convenience it's also possible to use an attribute path together with a single value as arguments. This is especially useful if the widget gets pre-configured in an outer describe block and is adjusted deeper in a nested structure.

This means that alternatively we could have written:

beforeEach( () => {
   axMocks.widget.configure( 'example.resource', 'exampleResource' );
   axMocks.widget.configure( 'example.action', 'exampleAction' );
} );

4. Optional: Configuring and/or Replacing Widget Services

Next, some of the LaxarJS widget services are modified for the test. Because LaxarJS Mocks automatically injects service mocks with appropriate Jasmine spies for all widget services provided by LaxarJS, you will often be able to skip this step. In this case, we'd like to test the URLs generated by the widget, so we modify the axFlowService mock to generate URLs of our choosing, that we can check against our expectations below.

Note that this callback has to complete synchronously. You cannot complete the surrounding beforeEach block by calling its done parameter from this callback because LaxarJS Mocks only stores the callback for now, and will only run it when axMocks.widget.load is called.

5. Loading the Widget

Here we tell LaxarJS to actually instantiate the widget controller. Since the API of the LaxarJS widget loader is asynchronous, the axMocks.widget.load method is asynchronous as well and thus expects a Jasmine done callback. Again, to keep tests simple, load doesn't need to be called directly, but can be passed directly to beforeEach. Make sure to load the widget only after all configuration and service mocks have been prepared, because afterwards calls to axMocks.widget.configure and axMocks.widget.whenServicesAvailable will have no effect.

6. Optional: Simulating Startup Events

When a page is loaded within a LaxarJS application, the runtime publishes several lifecycle events. Many widgets don't actually care for these events and only subscribe to custom events such as didReplace or takeActionRequest. Other widgets may depend on core events like didChangeLocale or didNavigate with certain parameters.

For testing a widget's response to core events without duplicating too much of the associated logic within every test, the function axMocks.triggerStartupEvents publishes all events that the runtime would publish, in the same order.

The method also allows to modify these events or skip some of them completely. For further information on event configuration have a look at the API docs.

7. Optional: Rendering the Widget DOM

Naturally this step does not apply to activities, since they do not influence the DOM and in particular have no visual representation. Calling axMocks.widget.render() for activities isn't harmful, but simply has no effect.

For widgets, their template is processed by the underlying technology adapter, wrapped in a DIV element and the resulting DOM node returned. This is not different from the rendering process in a regular application. Instead of appending the DOM to a widget area, it is appended to the body element of the test. It is removed again before the next test run would render its DOM or when calling axMocks.tearDown.

8. Tests

Now you're set up to write your actual tests. At this point the widget controller is instantiated, the (optional) DOM fragment has been rendered and all relevant runtime events were published.

Probably you want to group your tests into functional use cases via Jasmine describe functions. In this case it is sometimes a good thing to postpone the calls to axMocks.widget.load, axMocks.widget.render and axMocks.triggerStartupEvents. This allows you to structure the test in isolated describe blocks, to adjust configuration and service mocks for each block as needed and only afterwards to actually instantiate the widget and fire up your tests.

9. Optional: DOM-Tests

Using the DOM rendered in step 8, you can now run DOM-tests to check your widget's HTML representation. This is only relevant for widgets that you would like to write DOM-tests for. We recommend using the standard DOM APIs for this, but nothing prevents you from using a helper library such as jQuery.

10. Testbed Tear-Down

Every widget test should call axMocks.tearDown in a Jasmine afterEach block or simply pass it to afterEach as a callback. This ensures that after each test run the DOM is cleaned up and the widget with all its dependencies is destroyed. If this is omitted, it cannot be guaranteed previous test runs do not influence the current test run.

Note that you should avoid changing global state in your widget module, as LaxarJS Mocks cannot detect nor revert those changes during tear-down. However, side-effecting widget services such as axStorage are provided as mocks and are automatically reset between test runs.

More Information

For more in-depth documentation, refer to the API docs.