Invision UI v.19.6.0-0

Getting Started

  • Running Invision UI
  • UI Component Anatomy
  • Authoring SCSS
  • Authoring Presentational Behavior

Component Foundations

  • SCSS Authoring
    • Mixins & Variables
    • Stateful Classes
  • State Guidance
    • Empty/Zero State
    • Loading State
  • Utilities
    • Align
    • Display
    • Flexbox
    • Font Size
    • Layout
    • Offset
    • Position
    • Overflow
    • Size
    • Spacing
    • Text
  • Layout
    • Arrange [Deprectated]
    • Divided Columns
    • Embed Video
    • Full and Center
    • Fill [Deprectated]
    • Grid
    • Media Blocks
    • Panels
    • Vertical Layouts
  • Media Queries
  • Angular Behaviors
    • App Theme Directive
    • Auto Focus Input
    • Filters
    • Popup (modal dialog) Manager
    • Tether (tooltip/dropdown) Manager
    • Resize Sensor
    • Watchers

UI Component Library

  • Content & Containment
    • Active Translation
    • Auto Fit Text
    • Address
    • Basic & Input Table
    • Brand Logo/Slogan
    • Call to Action - Empty State
    • Carousel
    • Cart
    • Choice
    • Collapsible Container
    • Copyright Footer
    • Coupon Redeemer
    • Content
    • Date Relative Display
    • Definition Lists
    • Delimited List
    • Details
    • Expiration Date Display
    • Feature Toggle
    • Form Section Heading
    • Header
    • Heading
    • Heading With Annotation
    • Icons
    • Interaction Details
    • Labeled Balance
    • Link
    • Main Container
    • Metadata Card
    • Metadata List Item
    • Offering QuickView
    • Payment Instrument
    • Preference Dialog
    • Pricing Plan Decoration
    • Product Availability
    • Product Details
    • Product Image
    • Quick Info Panel
    • Remark
    • Renewal Column
    • Richlist Filterbar
    • Richlist Wrapper
    • Scrollable Horizontal List
    • Search Card
    • Search Panel
    • Section
    • Selectable Option
    • Smart Truncate
    • Status Grid
    • Tooltip
  • Data Visualizations
    • Bill Runs
    • Comparison Table
    • Datatable
    • Progress Tracker
    • Schedules
    • Query Builder
  • Dialogs
    • Confirmation Dialog
    • Dialog
    • Rich List Dialog
    • Wait Dialog
  • Forms and Filters
    • Additional Properties
    • Autocomplete
    • 3 Field Date Input
    • Checkbox
    • Credit Card
    • Bulk Action Bar
    • Buttons
    • Confirmation Button
    • Date Picker
    • E-Check
    • Entity Specification
    • External Bill
    • External Gift Card
    • Focus Group
    • Forms
    • Filter Bar
    • Filter Dropdowns
    • Gift Card
    • Grouped Check Filters
    • Inputs
    • Input Select Combo
    • Monetary Input
    • Multi Dropdown Selector
    • Multiple Numeric Input
    • Monetary Range Input
    • Password Unmask Button
    • Select
    • Select Button Or Stepper
    • Select Or Text Input
    • Stepper
    • Tag Dropdown
  • Navigation & Flow
    • Back Banner
    • Back Component
    • Breadcrumbs
    • Details Pager
    • Expandable Section
    • Infinite Scroll
    • Open New Window
    • Pager
    • Fancy Richlist
    • Standard Navigator
    • Status Indicator
    • Step Progress Bar
    • Step Progress Indicator
    • Tab Card
    • Tab Panel
    • Tier Navigator
    • Wizard
  • Notifications
    • Loading Indicator
    • Toast Notifications
  • Deprecated
    • Content Area

Showcase

  • General
    • Simplelist
    • Richlist
    • Primary (side) Navigator
  • Customer Care
    • Case Activity List
    • Customer Care Navigator Component
    • Dashboard
    • Decisions
  • Reporting
    • Content Visualization

Unit Testing

  • Actions
  • Components
  • Selectors
  • Reducers

End-to-end Testing

Statistics

Selectors

In this section

Overview

Pattern for Testing Selectors

Because Javascript is not a strongly typed language, we must consider side-effects when testing. Selectors in particular are based on a hierarchy and often one selector is used as the input for another. Because we don't have the benefit of type enforcement, it is possible for the output of a selector to change structurally, which will impact downstream selectors. Because of this, it is not wise to use resultFunc to supply the inputs for all selector tests. At a minimum, the happy paths (successful test scenarios) should be tested against a full run of the selector, which includes execution of the selectors that supply it with inputs.

In order to accomplish this cleanly, we start with our associated reducers as the base and define our successful test case data by making a modified copy. This allows us to retain immutability of our data and allows us to continue to define our data in a hierarchical fashion, starting with known sensible defaults and only modifying the pieces of data that need to be modified for the particular test(s) at hand. This also has the effect of making it easier to modify data for negative testing scenarios using simple setIn calls instead of standing up new data structures to inject via resultFunc. Ultimately, our tests become higher valued, cleaner and more readable.

It is important to note that data defined in our spec's data file should be good, clean data and should result in successful test completion. Negative testing data should be defined within the tests themselves to signal intent and will be demonstrated later in this document.

Defining Test Data

This is an example from products.order.selectors.spec.data.js. The naming convention tells us that this data file belongs with the products.order.selectors.spec.js testing file. In this example, we import the base customerCare spec data and apply the reducer configuration from products.order.reducer.js as the INITIAL_STATE. This gives us an initial unpopulated data store and also a base for the remainder of our data.

Data File Setup Example

javascript
import {CUSTOMER_CARE_STORE} from './customercare.spec.data.js';
import {INITIAL_STATE} from '../products.order.reducer';
import {PRICING_PLAN_TYPES} from '../constants/pricingplan.type.constants';
import {isNil} from 'ramda';

export const DEFAULT_PRODUCT = createProduct(1234);
export const DEFAULT_PRODUCT_PATH = ['customercare', 'productOrder', 'data', 'productsWithMetadata', '1234'];

Also note the two additional helper constants that we've created. The DEFAULT_PRODUCT constant give us a default product to be used for our store when we need to populate it with a product. We create the default product with a helper method that accepts the product Id as a parameter. Because of this, when we create a populated store and build upon that data later, we have a default product with known values we can use to reference it without the need to recreate it for every test. The DEFAULT_PRODUCT_PATH constant gives us the path to that product in the store so we can easily reference it later. These types of helpers may or may not be necessarily and can vary depending on your testing scenario, but consider them when common pieces of data will be used across multiple tests. It not only creates consistency between tests, but eases refactoring when data structures change as well.

This is an example of the setup for the INITIALIZED_STORE constant. For simple cases you can define your constants inline. In this case, much of the data has a bit more complex setup so we've chosen to use methods to initialize the data. Note how we created a default empty customer, not creating any data that's not necessary for any of our tests, and utilize setIn to combine our customer and our INITIAL_STATE with the CUSTOMER_CARE_STORE constant to create our initialized store data and avoid unnecessary duplication of json.

Data Setup Example

javascript
export const INITIALIZED_STORE = initializeInitialStore();

function initializeInitialStore() {
    const customer = {
        selectedCustomer: {
            subscriptions: {}
        }
    };
    return CUSTOMER_CARE_STORE
        .setIn(['customercare', 'customer'], customer)
        .setIn(['customercare', 'productOrder'], INITIAL_STATE);
}

From there, we are able to continue defining our data based on hierarchy. We now have an initialized store we can use to execute our tests when no product data exists and we can further define our store by adding the default product as follows:

Further Defining Data Hierarchy

javascript
export const POPULATED_STORE = initializePopulatedStore();

function initializePopulatedStore() {
    const productOrderData = {
        availableProducts: {
            data: {
                productsMap: {
                    1234: DEFAULT_PRODUCT
                },
                productsDisplayOrder: [DEFAULT_PRODUCT.Id],
                pageNumber: 1,
                recordCount: 1,
                searchString: '',
                selectedProductId: DEFAULT_PRODUCT.Id
            }
        },
        productsWithMetadata: {
            1234: DEFAULT_PRODUCT
        },
        shoppingCart: {
            Items: []
        },
        shoppingCartProductsMetadata: {}
    };
    return INITIALIZED_STORE.setIn(['customercare', 'productOrder', 'data'], productOrderData);
}

As you can see, we've further built our data by using the INITIALIZED_STORE as a starting point and adding in our base product data to define POPULATED_STORE.

Leveraging Our Test Data

With the test data defined as above, it is now easy to import, expand upon and leverage for both our positive and negative testing purposes and the separation of data from test code keeps our tests clean. We can utilize the data we've created to produce our successful test scenarios and modify bits of that data within the tests to form our negative testing scenarios. This abbreviated example demonstrates importing our data and performing both positive and negative testing.

Testing Example

javascript
import * as selectors from './products.order.selectors';
import * as TestData from './products.order.selectors.spec.data.js';
import Immutable from 'seamless-immutable';
import {
    expect
} from 'chai';

describe('IsCalculatingQuoteSelector with initial store', () => {
    it('should return false', () => {
        const appStore = TestData.INITIALIZED_STORE;
        const response = selectors.IsCalculatingQuoteSelector(appStore);
        expect(response).to.be.false;
    });
});

describe('IsCalculatingQuoteSelector while calculating order quote', () => {
    it('should return true', () => {
        const appStore = TestData.POPULATED_STORE.setIn(['customercare', 'productOrder', 'isCalculatingQuote'], true);
        const response = selectors.IsCalculatingQuoteSelector(appStore);
        expect(response).to.be.true;
    });
});

As you can see, we've created an initial test based on our INITIALIZED_STORE constant. Next, we created an additional test, this time using our POPULATED_STORE constant, but leveraging a setIn to modify the isCalculatingOrderQuote property. This allows us to make simple data changes without muddying our test files with verbose blocks of json. It makes it immediately clear to future maintainers exactly what the data modification is and it keeps maintainability high in that structural changes are made in the test data file and only setIn paths must be updated for the tests to be correct.