Unit testing

It is good practice for code destined for production to have automated unit tests.

This helps proactively identify and prevent issues, improve code quality, and makes it easier to safely refactor and update your Placement code when future requirements come in.

To help with unit testing Coveo Merchandising Hub (CMH) has provided a utility (@qubit/jest) to help write Placement tests with the popular testing framework jest.

Here is how it works:

Cloning an existing Placement

  1. Clone your Placement with Qubit CLI qubit placement clone {propertyId} {placementId}

  2. Install jest and @qubit/jest

    $ npm install --save-dev @qubit/jest jest
  3. Add the following to your package.json

      "scripts": {
        "test": "jest --coverage"
      },
      "jest": {
        "transform": {
          ".*(.js|.css|.less)$": "@qubit/jest"
        },
        "transformIgnorePatterns": []
      }
  4. Create a placement.test.js file

  5. You can now start writing tests! Execute them with the following command:

    $ npm test

Many example test suites for common kinds of Placement builds can be found here. But to start with, lets look at a test suite for a simple badging Placement:

const setup = require('@qubit/jest/setup')
const content = require('./payload.json')
const renderPlacement = require('./placement')

describe('placement.js', () => {
  let fixture

  afterEach(() => {
    // The teardown ensures that any cleanup methods registered with `onRemove` are called before the next test runs
    fixture.teardown()
    document.body.innerHTML = ''
  })

  describe('with content', () => {
    beforeEach(() => {
      // Our setup convenience method provides us with a mock api to call our `renderPlacement` function with
      // You can override this api by passing in more attributes
      // In this case you are passing in your `payload.json` to simulate `renderPlacement` being called with actual content
      // You are also passing in an element that the Placement is polling for, because it expects this to be on the page before rendering. This is the product element you will be rendering a badge for
      fixture = setup({ content, elements: [createTarget()] })
      return renderPlacement(fixture.api)
    })

    // Here you're checking that the message from the content payload was rendered correctly
    it('renders the message', () => {
      expect(document.querySelector('.BadgePill').innerHTML).toEqual(
        expect.stringContaining(content.message)
      )
    })

    // Here you're checking that our onImpression function was called
    it('calls onImpression', () => {
      expect(fixture.api.onImpression.mock.calls.length).toBe(1)
    })

    // Here you're checking that our onClickthrough function was called after clicking our badge
    it('calls onClickthrough', () => {
      expect(fixture.api.onClickthrough.mock.calls.length).toBe(0)
      document.querySelector('.BadgePill').click()
      expect(fixture.api.onClickthrough.mock.calls.length).toBe(1)
    })

    // Here you're checking that you've registered cleanup functions that correctly undo any side effects
    it('cleans up after itself', () => {
      const el = document.querySelector('.BadgePill')
      expect(document.body.contains(el)).toBe(true)
      fixture.teardown()
      expect(document.body.contains(el)).toBe(false)
    })
  })

  describe('with null content', () => {
    beforeEach(() => {
      fixture = setup({ content: null, elements: [createTarget()] })
      return renderPlacement(fixture.api)
    })

    it('calls onImpression', () => {
      // Here you're checking that our Placement also executes when the content is null
      // And that you correctly call the onImpression event
      expect(fixture.api.onImpression.mock.calls.length).toBe(1)
    })
  })
})

// Here you are setting up any elemenets that are expected to be on the page
function createTarget () {
  const el = document.createElement('div')
  el.className = 'Target'
  el.innerHTML = `<div/>`
  document.body.append(el)
  return el
}

Creating a new Placement

  1. Create a Placement with Qubit CLI qubit placement create {propertyId}. This will create all of the files and install the dependencies you need both for your Placement and its tests. The Placement will be created in a new folder with a name like placement-{propertyId}-{placementId}. The Placement Id is automatically generated when you create it

  2. Once created, open your Placement’s UI by navigating to the newly created folder:

    $ cd [new-folder]

    and running

    $ qubit placement open
  3. This will open your Placement in Coveo Merchandising Hub (CMH), where you can set your triggers, schema (personalized content only), product settings (Recommendations only) and sample payload (more about those here). Once you are done, go to your placement folder and pull your changes by running qubit placement pull

  4. Now you are ready to start developing your placement, and when you are ready to test it, you will find some boilerplate code in placement.test.js.

    To run your tests:

    $ npm test