1.0.276 • Published 3 years ago

turnerjs v1.0.276

Weekly downloads
1,116
License
-
Repository
github
Last release
3 years ago

Angular Components Test Kit

Post

A detailed post with examples and explanations can be found in the following Medium article:

Testing angular components — you don’t know what you’re missing

Overview

Component tests allows testing the UI (provided that it is build as components/directives of course) in order to get instant feedback and minimize the use of protractor.

The test kit provides a base driver utility class - TurnerComponentDriver, which allows writing tests using typescript in order to test components in a more readable, maintainable and quicker manner, It provides basic methods to allow rendering a template, accessing underlying elements and ability to maintain drivers' hierarchy, in order to be able to reuse drivers in parent components.

The best practice implemented by TurnerJS in regards to elements' selectors is by using a 'data-hook' attribute in order to mark elements which are accessed by your code. It implements the approach demonstrates in this article, but it is not mandated by the framework, one can use any other data selectors approach.

Installation - Bower

  1. install using bower
    bower install --save turnerjs
  2. Include the following reference in your Karma configuration file
    '<path to your app>/bower_components/turnerjs/dist/test/lib/turnerjs-driver.js'
  3. Optional - if you are using TypeScript (recommended) add reference to the d.ts file in your tsconfig file:
    "bower_components/turnerjs/dist/test/lib/turnerjs-driver.d.ts"

Installation - NPM

  1. install using node package manager
    npm install -S turnerjs
  2. Include the following reference in your Karma configuration file
    'node_modules/turnerjs/module/generated/turnerjs-driver.js' or
    require(turnerjs) (when window is supported)
  3. Optional - if you are using TypeScript (recommended) add reference to the d.ts file in your tsconfig file:
    "../node_modules/turnerjs/module/generated/turnerjs-driver.d.ts"

Usage

  • Create a driver that extends the base driver and implement the methods that are required for your tests, for example (in the spec file or elsewhere):
   class ExampleComponentDriver extends TurnerComponentDriver {
           
        constructor() {
          super();
        }
      
        render(param1, param2) {
          //renders the template and adds the input parameters as scope params (second argument to renderFromTemplate method)
          //passing a selector is needed when the element containing the directive is not the first element in the template or when there is more than one driver
          this.renderFromTemplate('<example-component another-attribute="param2"><div data-hook="inner-element">{{param1}}</div></example-component>', {param1, param2}, 'example-component');
        }
      
        isInnerElementValid(): boolean {
           return this.findByDataHook('inner-element').text() === 'valid';
        }
      }
  • Write tests that utilizes the driver
    let driver: ExampleComponentDriver;
    beforeEach(() => {
       driver = new ExampleComponentDriver();
       driver.render('valid', 'yada yada');
       driver.connectToBody();
    });
   
    afterEach(() => {
        driver.disconnectFromBody();
    });
    
    it('should contain a valid element value', () => {
        expect(driver.isInnerElementValid()).toBeTruthy();
    });

Global Methods added by the test kit

ParamTypeArgumentsDetails
byDataHookglobal methoddataHook: stringCreate a data-hook selector it is useful for methods that uses selectors such as TurnerComponentDriver.defineChild

Base Driver Methods/ Members

ParamTypeArgumentsDetails
constructorConstructorN/ACreates the driver
renderFromTemplateprotected methodtemplate: string, args?: Object, selector?: string, appendToBody?: booleanAllows rendering the component/directive, the args is a key value pairs object that will be added to the scope of the element, initializes the root of the driver according to the selector
findByDataHookprotected methoddataHook: stringa utility method that should be used by drivers that inherits from the base driver in order to select an element (first if there are several) by data-hook attribute. It will throws an error if called before renderFromTemplate was called
findAllByDataHookprotected methoddataHook: stringsimilar to findByDataHook but allows selecting several elements with the same data-hook
defineChildprotected methodchildDriver: Instance of T such that T extends TurnerComponentDriver, selector: string representing a CSS selector (preferably called with byDataHook(dataHook))Declare a child driver of the current driver, allows components hierarchy, which is also returned by the method. This method should be called before renderFromTemplate was called
defineChildrenprotected methodfactory: function that returns an instance of T such that T extends TurnerComponentDriver, selector: string representing a CSS selector (which is expected to return more than one result)returns an array of child drivers (instances of T), it is useful when there is more than one child driver for parent driver (e.g. ng-repeat), the returned array will change when there is a change in the number of elements in the dom. This method should be called before renderFromTemplate was called
applyChangespublic methodN/Ainvokes $rootScope.$digest(), mainly aimed to 'hide' AngularJS related implementation
connectToBodypublic methodN/AConnects the template to the karma's browser body - allows height/width related tests. disconnectFromBody has to be called at the end of the test. It will thorw an error if called before renderFromTemplate was called
disconnectFromBodypublic methodN/AClears the the body of the karma's browser, used in order to reset the browser to the original state prior to starting the next test
afterRenderprotected methodN/AYou can override this method if you need extra setup after render
elementng.IAugmentedJQueryN/AReference to the element that represents the root of the driver (by selector if provided or template root)
scopeng.IScopeN/AReference to the scope of element member
isRenderedbooleanN/Adefines whether the driver is rendered - whether its template was rendered and its scope is valid (defined and part of the dom)
appendedToBodybooleanN/Adefines whether the driver's element is appended to body (e.g. a driver for bootstrap tooltip)
$rootScopeng.IRootScopeServiceN/AReference to the $rootScope service (removes the need to inject it in tests)

Nested drivers

In order to allow reuse of drivers, the base driver supports initializing any child element (member) that extends TurnerComponentDriver For example, assuming 3 components are defined:

   angular.module('myModule', []);
   class ItemComponentCtrl {
     public item: {value: number};

     isValid():  boolean {
       return this.item.value > 1;
     }
   }
   
   angular
     .module('myModule')
     .component('itemComponent', {
       template: `<div data-hook="item-element" ng-class="{'valid': $ctrl.isValid()}"/>`,
       controller: ItemComponentCtrl,
       bindings: {
         item: '='
       }
     });
   
   class IndividualComponentCtrl {
     public item: {value: number};

     getText():  string {
       return this.item.value > 1 ? 'valid' : 'invalid';
     }
   }
   
   angular
     .module('myModule')
     .component('individualComponent', {
       template: `<div data-hook="inner-element">{{$ctrl.getText()}}</div>`,
       controller: IndividualComponentCtrl,
       bindings: {
         item: '='
       }
     });
   
   class ParentComponentCtrl {
     public items: {value: number}[];
   
     constructor() {
       this.items = [];
       for (let i = 0; i < 5; i++) {
         //push either 1 or 2
         this.items.push({
           value: Math.floor((Math.random() * 2) + 1)
         });
       }
     }
   }
   
   angular
     .module('myModule')
     .component('parentComponent', {
       template: `<div>
                    <individual-component item="$ctrl.items[0]"/>
                    <item-component ng-repeat="item in $ctrl.items" item="item"/>
                  </div>`,
       controller: ParentComponentCtrl
     });

3 Drivers that corresponds to each are defined:
(When there is a list of child drivers - e.g. when using ng-repeat, defineChildren method should be used in order to declare an array of child drivers)

  
class IndividualComponentDriver extends TurnerComponentDriver {

  constructor() {
    super();
  }

  render(item) {
    this.renderFromTemplate('<individual-component item="item"/>', {item});
  }

  isValid(): boolean {
    return this.findByDataHook('inner-element').text() === 'valid';
  }
}

class ItemComponentDriver extends TurnerComponentDriver {

  constructor() {
    super();
  }

  render(item) {
    this.renderFromTemplate('<item-component item="item"/>', {item}, 'item-component');
  }

  isValid(): boolean {
    return this.findByDataHook('item-element').hasClass('valid');
  }
}

class ParentComponentDriver extends TurnerComponentDriver {
  public innerComponent: IndividualComponentDriver;
  public itemComponents: ItemComponentDriver[];

  constructor() {
    super();
    this.innerComponent = this.defineChild(new IndividualComponentDriver(), 'individual-component');
    this.itemComponents = this.defineChildren(() => new ItemComponentDriver(), 'item-component');
  }

  render() {
    this.renderFromTemplate(`<parent-component>`);
  }

  isIndividualValid(): boolean {
    return this.innerComponent.isValid();
  }

  isItemsValid(): boolean {
    let result = true;
    this.itemComponents.forEach(itemDriver => {
      result = result && itemDriver.isValid();
    });
    return result;
  }
}

TurnerComponentDriver will initialize the member's scope & element automatically as soon as the renderFromTemplate method is invoked. The above drivers will allow testing each component separately and also testing the parent component that wraps the two:

describe('Usage Examples when there are repeatable drivers', () => {
    let parentComponentDriver: ParentComponentDriver;
    beforeEach(() => {
      module('myModule');
      parentComponentDriver = new ParentComponentDriver();
    });

    it('should be valid when the random values are above 1', () => {
      spyOn(Math, 'random').and.returnValue(0.9);
      parentComponentDriver.render();
      expect(parentComponentDriver.isIndividualValid()).toBe(true);
      expect(parentComponentDriver.isItemsValid()).toBe(true);
    });
  });

Non TypeScript usage

Though the recommendation is to use TypeScript with turnerjs, if you are not using it, you can create the driver using prototypical inheritance. There are various ways to implement it, but the test kits includes a test spec for ES5 usage (see app/test/spec/components/es5-name-formatter.js) The below provides the basic implementation of such driver:

/* globals TurnerComponentDriver */ //for jshint
function ES5Driver() {
  TurnerComponentDriver.call(arguments);
}

ES5Driver.prototype = new TurnerComponentDriver();
ES5Driver.constructor = ES5Driver;

ES5Driver.prototype.render = function () {
  this.renderFromTemplate('<es5-component-template></es5-component-template>');  
};
//other driver methods

describe('your tests are here', function () {
    var es5driver;
    
    beforeEach(function () {
        angular.mock.module('turnerjsAppInternal');
        driver = new ES5Driver();
    });
    
    it('...', function () {
        driver.render();
        //test e
    });
});

Contribution

Via pull requests,

After cloning the repository please run npm install and bower install in order to fetch dependencies

Running/Building the project is done by using grunt:
grunt serve:clean - will start the server, there is no real UI for it, but it will run the unit tests on each save grunt build - build the project to make sure that changes are valid and meeting all code style definitions

Credits

Alon Yehezkel
Shahar Talmi
Boris Litvinski
Amit Shvil

License

The MIT License.

See LICENSE

0.0.0

3 years ago

1.0.276

5 years ago

1.0.275

6 years ago

1.0.274

6 years ago

1.0.273

6 years ago

1.0.272

7 years ago

1.0.271

7 years ago

1.0.270

7 years ago

1.0.269

7 years ago

1.0.268

7 years ago

1.0.267

7 years ago

1.0.266

7 years ago

1.0.265

7 years ago

1.0.264

7 years ago

1.0.263

7 years ago

1.0.262

7 years ago

1.0.261

7 years ago

1.0.260

7 years ago

1.0.259

7 years ago

1.0.258

7 years ago

1.0.257

7 years ago

1.0.256

7 years ago

1.0.255

7 years ago

1.0.254

7 years ago

1.0.253

7 years ago

1.0.252

7 years ago

1.0.251

7 years ago

1.0.250

7 years ago

1.0.249

7 years ago

1.0.248

7 years ago

1.0.247

7 years ago

1.0.246

8 years ago

1.0.245

8 years ago

1.0.244

8 years ago

1.0.243

8 years ago

1.0.242

8 years ago

1.0.241

8 years ago

1.0.240

8 years ago

1.0.239

8 years ago

1.0.238

8 years ago

1.0.237

8 years ago

1.0.236

8 years ago

1.0.235

8 years ago

1.0.234

8 years ago

1.0.233

8 years ago

1.0.232

8 years ago

1.0.231

8 years ago

1.0.230

8 years ago

1.0.229

8 years ago

1.0.228

8 years ago

1.0.227

8 years ago

1.0.226

8 years ago

1.0.225

8 years ago

1.0.224

8 years ago

1.0.223

8 years ago

1.0.222

8 years ago

1.0.221

8 years ago

1.0.220

8 years ago

1.0.219

8 years ago

1.0.218

8 years ago

1.0.217

8 years ago

1.0.216

8 years ago

1.0.215

8 years ago

1.0.214

8 years ago

1.0.213

8 years ago

1.0.212

8 years ago

1.0.211

8 years ago

1.0.210

8 years ago

1.0.209

8 years ago

1.0.208

8 years ago

1.0.207

8 years ago

1.0.206

8 years ago

1.0.205

8 years ago

1.0.204

8 years ago

1.0.203

8 years ago

1.0.202

8 years ago

1.0.201

8 years ago

1.0.200

8 years ago

1.0.199

8 years ago

1.0.198

8 years ago

1.0.197

8 years ago

1.0.196

8 years ago

1.0.195

8 years ago

1.0.194

8 years ago

1.0.193

8 years ago

1.0.192

8 years ago

1.0.191

8 years ago

1.0.190

8 years ago

1.0.189

8 years ago

1.0.188

8 years ago

1.0.187

8 years ago

1.0.186

8 years ago

1.0.185

8 years ago

1.0.184

8 years ago

1.0.183

8 years ago

1.0.182

8 years ago

1.0.181

8 years ago

1.0.180

8 years ago

1.0.179

8 years ago

1.0.178

8 years ago

1.0.177

8 years ago

1.0.176

8 years ago

1.0.175

8 years ago

1.0.174

8 years ago

1.0.173

8 years ago

1.0.172

8 years ago

1.0.171

8 years ago

1.0.170

8 years ago

1.0.169

8 years ago

1.0.168

8 years ago

1.0.167

8 years ago

1.0.166

8 years ago

1.0.165

8 years ago

1.0.164

8 years ago

1.0.163

8 years ago

1.0.162

8 years ago

1.0.161

8 years ago

1.0.160

8 years ago

1.0.159

8 years ago

1.0.158

8 years ago

1.0.157

8 years ago

1.0.156

8 years ago

1.0.155

8 years ago

1.0.154

8 years ago

1.0.153

8 years ago

1.0.152

8 years ago

1.0.151

8 years ago

1.0.150

8 years ago

1.0.149

8 years ago

1.0.148

8 years ago

1.0.147

8 years ago

1.0.146

8 years ago

1.0.145

8 years ago

1.0.144

8 years ago

1.0.143

8 years ago

1.0.142

8 years ago

1.0.141

8 years ago

1.0.140

8 years ago

1.0.139

8 years ago

1.0.138

8 years ago

1.0.137

8 years ago

1.0.136

8 years ago

1.0.135

8 years ago

1.0.134

8 years ago

1.0.133

8 years ago

1.0.132

8 years ago

1.0.131

8 years ago

1.0.130

8 years ago

1.0.129

8 years ago

1.0.128

8 years ago

1.0.127

8 years ago

1.0.126

8 years ago

1.0.125

8 years ago

1.0.124

8 years ago

1.0.123

8 years ago

1.0.122

8 years ago

1.0.121

8 years ago

1.0.120

8 years ago

1.0.119

8 years ago

1.0.118

8 years ago

1.0.117

8 years ago

1.0.116

8 years ago

1.0.115

8 years ago

1.0.114

8 years ago

1.0.113

8 years ago

1.0.112

8 years ago

1.0.111

8 years ago

1.0.110

8 years ago

1.0.109

8 years ago

1.0.108

8 years ago

1.0.107

8 years ago

1.0.106

8 years ago

1.0.105

8 years ago

1.0.104

8 years ago

1.0.103

8 years ago

1.0.102

8 years ago

1.0.101

8 years ago

1.0.100

8 years ago

1.0.99

8 years ago

1.0.98

8 years ago

1.0.97

8 years ago

1.0.96

8 years ago

1.0.95

8 years ago

1.0.94

8 years ago

1.0.93

8 years ago

1.0.92

8 years ago

1.0.91

8 years ago

1.0.90

8 years ago

1.0.89

8 years ago

1.0.88

8 years ago

1.0.87

8 years ago

1.0.86

8 years ago

1.0.85

8 years ago

1.0.84

8 years ago

1.0.83

8 years ago

1.0.71

8 years ago

1.0.70

8 years ago

1.0.69

8 years ago

1.0.68

8 years ago

1.0.67

8 years ago

1.0.66

8 years ago

1.0.65

8 years ago

1.0.64

8 years ago

1.0.63

8 years ago

1.0.62

8 years ago

1.0.61

8 years ago

1.0.60

8 years ago

1.0.59

8 years ago

1.0.58

8 years ago

1.0.57

8 years ago

1.0.56

8 years ago

1.0.55

8 years ago

1.0.54

8 years ago

1.0.53

8 years ago

1.0.52

8 years ago

1.0.51

8 years ago

1.0.50

8 years ago

1.0.49

8 years ago

1.0.48

8 years ago

1.0.47

8 years ago

1.0.46

8 years ago

1.0.45

8 years ago

1.0.44

8 years ago

1.0.43

8 years ago

1.0.42

8 years ago

1.0.41

8 years ago

1.0.40

8 years ago

1.0.39

8 years ago

1.0.38

8 years ago

1.0.37

8 years ago

1.0.36

8 years ago

1.0.35

8 years ago

1.0.34

8 years ago

1.0.33

8 years ago

1.0.32

8 years ago

1.0.31

8 years ago

1.0.30

8 years ago

1.0.29

8 years ago

1.0.28

8 years ago

1.0.27

8 years ago

1.0.26

8 years ago

1.0.25

8 years ago

1.0.24

8 years ago

1.0.23

8 years ago

1.0.22

8 years ago

1.0.21

8 years ago

1.0.20

8 years ago

1.0.19

8 years ago

1.0.18

8 years ago

1.0.17

8 years ago

1.0.16

8 years ago

1.0.15

8 years ago

1.0.14

8 years ago

1.0.13

8 years ago

1.0.12

8 years ago

1.0.11

8 years ago

1.0.10

8 years ago

1.0.9

8 years ago

1.0.8

8 years ago

1.0.7

8 years ago

1.0.6

8 years ago

1.0.5

8 years ago

1.0.4

8 years ago

1.0.3

8 years ago

1.0.2

8 years ago

1.0.1

8 years ago

1.0.0

8 years ago