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

Unit Testing

In this section

Overview

The Invision team has committed to having 100% code coverage for two parts of the system: Reducers and Selectors. These two concepts underlie the primary storage mechanism for the application, a critical system. Any time a reducer or selector is added or modified, the corresponding tests should be amended to reflect these changes. While only selectors and reducers are required to be tested, it is possible to test Action Creators and Visual Components. It is expected that any code that contains sufficiently complex business logic, whether it's in a selector, reducer, action, component, helper or some other block of code, will be tested.

Best Practices

Test Names

Prefer names that describe what the system is doing, rather than naming the tests after specific functions. This allows the tests to double as living documentation of what the system should be doing, as opposed to only what it is doing. Other developers should be able to read test cases to get an understanding of what the feature is designed to do, and what types of behaviors they should expect. While we strive to make the names used in the application code be descriptive of their operation, every developer knows that maintaining this isn't always feasible or warranted. Test descriptions provide a better mechanism to capture this detail.

Do

javascript
describe('When the user is making a payment', () => {})

Don't

javascipt
describe('MakePaymentComponent.makePayment', () => {})

Assertions/Expectations

Because tests assert assumptions about the application code, it's best to isolate assertions (expectations) in a test block to that of a single assumption. This makes it more clear what is expected for a particular test case. Additionally, when a given assertion fails, the testing framework stops checking expectations for a test case. When a given it block has multiple expect statements and the first one fails, it could be masking that the other expect statements are failing as well. The only way to know is fix the broken expect and see if others in that function fail.

Do

javscript
it('Should set the user name', () => {
...
expect(user.name).to.eql('Bob');
});

it('Should set the available modules on the user', () => {
...
expect(user.modules).to.eql([{Id: 1}]);
});

Don't

javscript
it('Should fetch the user', () => {
...
expect(user.name).to.eql('Bob');
expect(user.modules).to.eql([{Id: 1}]);
});

Setup

Each test should be an independent unit that passes or fails on its own. Additionally, when a specific test fails, the easier it is to tell why it failed, the easier it will be to diagnose the problem with the system. Therefore, avoid using beforeEach except when absolutely necessary. Instead, prefer to use object creators with sensible defaults that can be called inside of a test, overriding any values as necessary.

One way to organize tests is to use Arrange, Act, Assert.

  • Arrange: Set up the necessary preconditions for a test
  • Act: Call the function under test
  • Assert: Verify that after calling the function under test, the correct result occurred

    Do

    javascript
    describe('And line of business is loaded', () => {
      it('Should not fetch the line of business data', () => {
          const ctrl = createController({ isLineOfBusinessLoaded: true });
          ctrl.$onInit();
          expect(ctrl.actions.fetchLineOfBusinessMetadata.called).to.be.false;
      });
    });

    Don't

    javascript
    describe('And line of business is loaded', () => {
      let ctrl = {};
    
      beforeEach(() => {
          ctrl = createController({isLineOfBusinessLoaded: true});
          ctrl.$onInit();
      });
    
      ...
    
      it('Should not fetch the line of business data', () => {
          expect(ctrl.actions.fetchLineOfBusinessMetadata.called).to.be.false;
      });
    });

    The test in the second case attempts to perform all of the setup in the beforeEach, as a result, when the test fails, the only thing the developer knows is that they expected something to be false and it was true. This could be because the test was set up incorrectly, or it could be that the code under test is broken. With the set up code being outside of the it block, it becomes a more difficult task to determine how the test was set up. Additionally, with nested describe blocks, each possibly having a beforeEach, it becomes a more arduous task of tracing through all of the set up block.

Spies, Stubs & Mocks

When using Sinon to mock dependencies, it will replace the functionality on a given object reference. This has impacts outside of the scope of the individual test, as some objects are singletons within the lifespan of the tests. Replacing a function using Sinon on i18n, for instance, will have large impacts on the application. When using spies, stubs or mocks, it is important to restore the functionality after the test is complete. Stubs will allow you to override a function to provide specific functionality for a specific test. If it is not restored after the test is run, it will impact other tests that are using that function.

Do

javascript
afterEach(() => {
    myModule.function.restore && myModule.function.restore();
});

This makes sure that restore exists on this object before trying to restore. This allows the function to be stubbed in some test cases, and not in others, and ensures it is always restored.

Examples

Stubbing i18n.translate

The i18n library is injected via Angular dependencies, and since the tests do not use Angular, the function needs to be stubbed or spied on. If you only have one translation call, you can use a simple stub to have it return a desired string

javascript
sinon.stub(i18n, 'translate').returns('My String');

A better approach, would be to use the withArgs function on stubs, to make sure that it matches with the key you'd expect

javascript
sinon.stub(i18n, 'translate').withArgs(LocaleKeys.PRODUCT.INTERACTION.INTERACTION).returns('Interaction');

You can then verify that the code under test calls translate with the expected argument (LocaleKeys.PRODUCT.INTERACTION.INTERACTION) and you can then verify that the right string is returned.

Updating State With a Stub

At times, particularly in components, it is common to mock an action call. For the most part these have been verified with a simple expect called to be true type of expectation. However, a better approach would be to use a function inside of the stub that can set the state of your controller. This way your test more accurately reflects what you're after. Instead of verifying that a function was called, you can verify that your controller now has the correct state

javascript
sinon.stub(ctrl.actions, 'setCurrentPage', () => {
    ctrl.state.currentPage = 1;
    return Promise.resolve();
});

This stubs out the setCurrentPage function. The third parameter passed to the stub in this case is a function that will be called. This allows the test to update the controller's state ({{currentPage}} here, and still resolve a promise. This, in effect, fakes out the function. In sinon 2.0 there is a function callsFake that would do something similar, if and when sinon is upgraded.

Unit Test Debugging

You can run npm run test:debug to use node to debug the mocha tests. It will open a new chrome window and then run the tests. You can then set breakpoints like you would debugging any web page. You'll have to save a file to get the tests to run again.

Please see Dev Machine Setup for details on setting up WebStorm for debugging.