DEV Community 👩‍💻👨‍💻

Amabe Dev
Amabe Dev

Posted on • Updated on

Unit testing AngularJS components with ng-model

I wanted to test a component using the UI elements to avoid testing to much implementation details.

When doing that, I faced was faced with an input and did not find easily how to trigger its change events to trigger the ng-model change and the ng-change event.

Here is how I did it. (This come late given that AngularJS is deprecated. But I guess I will not be the only one lucky enough to still work with it sometime.)

 

The test base

For these tests, we will use the angular-mocks library.

We will use le $compile to instanciate the component. And then interact with the component by using the compiled elements.

We will compile the input directly to make the tests easier. We could compile a component that have inputs and retrieve them by calling the JQuery .find(<selector>) on the compiled element.

We will use the Jest syntax for the test declaration and assertions.

require('angular');
require('angular-mocks');

describe('test', () => {
  let $rootScope;
  let $compile;
  let $timeout;

  beforeEach(inject((_$rootScope_, _$compile_, _$timeout_) => {
    $rootScope = _$rootScope_;
    $compile = _$compile_;
    $timeout = _$timeout_;
  }));

  // ...
});
Enter fullscreen mode Exit fullscreen mode

 

Simple ng-model test

In this test, we will use a simple input without ng-model-options.

To update the ng-model update and ng-change update we will use the following JQuery operations:
- .val(<value>) to update the value
- .triggerHandler('change') to send the change event of the input

it('Should trigger change ng-model value and trigger ng-change', () => {
  const scope = $rootScope.$new();
  scope.value = '';
  scope.onChange = jest.fn();

  const element = $compile('<input ng-model="value" ng-change="onChange()">')(scope);
  scope.$digest();

  element.val('test').triggerHandler('change');

  expect(scope.value).toBe('test');
  expect(scope.onChange).toHaveBeenCalled();
});
Enter fullscreen mode Exit fullscreen mode

 

ng-model with update on blur

In this test, we will use a simple input with ng-model-options to update only when the input blur event is triggered.

The blur event is triggered when the field looses focus.

This may be used to avoid updating every time the value changes if we know that we will only use the value when the input will be filled.

To update the ng-model update and ng-change update we will use the following JQuery operations:
- .val(<value>) to update the value
- .triggerHandler('change') to send the change event of the input
- .triggerHandler('blur') to send the blur event of the input

it('Should trigger change ng-model value and trigger ng-change (update on blur)', () => {
  const scope = $rootScope.$new();
  scope.value = '';
  scope.onChange = jest.fn();

  const element = $compile('<input ng-model="value" ng-model-options="{ updateOn: \'blur\' }" ng-change="onChange()">')(scope);
  scope.$digest();

  element.val('test').triggerHandler('change').triggerHandler('blur');

  expect(scope.value).toBe('test');
  expect(scope.onChange).toHaveBeenCalled();
});
Enter fullscreen mode Exit fullscreen mode

 

ng-model with debounce

In this test, we will use a simple input without ng-model-options.

The debounce option can be used to update the value only when the if it does not change for a given time.

This make it possible to keep up to date even if the input does not loose focus while avoiding getting too much events.

It can be useful for elements like search inputs where we want to show suggestions while the user types. But we do not want to do the computation on every change because it can be quite heavy.

To update the ng-model update and ng-change update we will use the following JQuery operations:
- .val(<value>) to update the value
- .triggerHandler('change') to send the change event of the input

Then we will use $timeout.flush() for the code to act like if the debounce time has passed.

it('Should trigger change ng-model value and trigger ng-change (debounce)', () => {
  const scope = $rootScope.$new();
  scope.value = '';
  scope.onChange = jest.fn();

  const element = $compile('<input ng-model="value" ng-model-options="{ debounce: 200 }" ng-change="onChange()">')(scope);
  scope.$digest();

  element.val('test').triggerHandler('change');
  $timeout.flush();

  expect(scope.value).toBe('test');
  expect(scope.onChange).toHaveBeenCalled();
});
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
davidwi53928361 profile image
David Wiese

🌚 Life is too short to browse without dark mode