1.0.276 • Published 5 years ago

turnerjs v1.0.276

Weekly downloads
1,116
License
-
Repository
github
Last release
5 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

5 years ago

1.0.276

7 years ago

1.0.275

8 years ago

1.0.274

8 years ago

1.0.273

8 years ago

1.0.272

9 years ago

1.0.271

9 years ago

1.0.270

9 years ago

1.0.269

9 years ago

1.0.268

9 years ago

1.0.267

9 years ago

1.0.266

9 years ago

1.0.265

9 years ago

1.0.264

9 years ago

1.0.263

9 years ago

1.0.262

9 years ago

1.0.261

9 years ago

1.0.260

9 years ago

1.0.259

9 years ago

1.0.258

9 years ago

1.0.257

9 years ago

1.0.256

9 years ago

1.0.255

9 years ago

1.0.254

9 years ago

1.0.253

9 years ago

1.0.252

9 years ago

1.0.251

10 years ago

1.0.250

10 years ago

1.0.249

10 years ago

1.0.248

10 years ago

1.0.247

10 years ago

1.0.246

10 years ago

1.0.245

10 years ago

1.0.244

10 years ago

1.0.243

10 years ago

1.0.242

10 years ago

1.0.241

10 years ago

1.0.240

10 years ago

1.0.239

10 years ago

1.0.238

10 years ago

1.0.237

10 years ago

1.0.236

10 years ago

1.0.235

10 years ago

1.0.234

10 years ago

1.0.233

10 years ago

1.0.232

10 years ago

1.0.231

10 years ago

1.0.230

10 years ago

1.0.229

10 years ago

1.0.228

10 years ago

1.0.227

10 years ago

1.0.226

10 years ago

1.0.225

10 years ago

1.0.224

10 years ago

1.0.223

10 years ago

1.0.222

10 years ago

1.0.221

10 years ago

1.0.220

10 years ago

1.0.219

10 years ago

1.0.218

10 years ago

1.0.217

10 years ago

1.0.216

10 years ago

1.0.215

10 years ago

1.0.214

10 years ago

1.0.213

10 years ago

1.0.212

10 years ago

1.0.211

10 years ago

1.0.210

10 years ago

1.0.209

10 years ago

1.0.208

10 years ago

1.0.207

10 years ago

1.0.206

10 years ago

1.0.205

10 years ago

1.0.204

10 years ago

1.0.203

10 years ago

1.0.202

10 years ago

1.0.201

10 years ago

1.0.200

10 years ago

1.0.199

10 years ago

1.0.198

10 years ago

1.0.197

10 years ago

1.0.196

10 years ago

1.0.195

10 years ago

1.0.194

10 years ago

1.0.193

10 years ago

1.0.192

10 years ago

1.0.191

10 years ago

1.0.190

10 years ago

1.0.189

10 years ago

1.0.188

10 years ago

1.0.187

10 years ago

1.0.186

10 years ago

1.0.185

10 years ago

1.0.184

10 years ago

1.0.183

10 years ago

1.0.182

10 years ago

1.0.181

10 years ago

1.0.180

10 years ago

1.0.179

10 years ago

1.0.178

10 years ago

1.0.177

10 years ago

1.0.176

10 years ago

1.0.175

10 years ago

1.0.174

10 years ago

1.0.173

10 years ago

1.0.172

10 years ago

1.0.171

10 years ago

1.0.170

10 years ago

1.0.169

10 years ago

1.0.168

10 years ago

1.0.167

10 years ago

1.0.166

10 years ago

1.0.165

10 years ago

1.0.164

10 years ago

1.0.163

10 years ago

1.0.162

10 years ago

1.0.161

10 years ago

1.0.160

10 years ago

1.0.159

10 years ago

1.0.158

10 years ago

1.0.157

10 years ago

1.0.156

10 years ago

1.0.155

10 years ago

1.0.154

10 years ago

1.0.153

10 years ago

1.0.152

10 years ago

1.0.151

10 years ago

1.0.150

10 years ago

1.0.149

10 years ago

1.0.148

10 years ago

1.0.147

10 years ago

1.0.146

10 years ago

1.0.145

10 years ago

1.0.144

10 years ago

1.0.143

10 years ago

1.0.142

10 years ago

1.0.141

10 years ago

1.0.140

10 years ago

1.0.139

10 years ago

1.0.138

10 years ago

1.0.137

10 years ago

1.0.136

10 years ago

1.0.135

10 years ago

1.0.134

10 years ago

1.0.133

10 years ago

1.0.132

10 years ago

1.0.131

10 years ago

1.0.130

10 years ago

1.0.129

10 years ago

1.0.128

10 years ago

1.0.127

10 years ago

1.0.126

10 years ago

1.0.125

10 years ago

1.0.124

10 years ago

1.0.123

10 years ago

1.0.122

10 years ago

1.0.121

10 years ago

1.0.120

10 years ago

1.0.119

10 years ago

1.0.118

10 years ago

1.0.117

10 years ago

1.0.116

10 years ago

1.0.115

10 years ago

1.0.114

10 years ago

1.0.113

10 years ago

1.0.112

10 years ago

1.0.111

10 years ago

1.0.110

10 years ago

1.0.109

10 years ago

1.0.108

10 years ago

1.0.107

10 years ago

1.0.106

10 years ago

1.0.105

10 years ago

1.0.104

10 years ago

1.0.103

10 years ago

1.0.102

10 years ago

1.0.101

10 years ago

1.0.100

10 years ago

1.0.99

10 years ago

1.0.98

10 years ago

1.0.97

10 years ago

1.0.96

10 years ago

1.0.95

10 years ago

1.0.94

10 years ago

1.0.93

10 years ago

1.0.92

10 years ago

1.0.91

10 years ago

1.0.90

10 years ago

1.0.89

10 years ago

1.0.88

10 years ago

1.0.87

10 years ago

1.0.86

10 years ago

1.0.85

10 years ago

1.0.84

10 years ago

1.0.83

10 years ago

1.0.71

10 years ago

1.0.70

10 years ago

1.0.69

10 years ago

1.0.68

10 years ago

1.0.67

10 years ago

1.0.66

10 years ago

1.0.65

10 years ago

1.0.64

10 years ago

1.0.63

10 years ago

1.0.62

10 years ago

1.0.61

10 years ago

1.0.60

10 years ago

1.0.59

10 years ago

1.0.58

10 years ago

1.0.57

10 years ago

1.0.56

10 years ago

1.0.55

10 years ago

1.0.54

10 years ago

1.0.53

10 years ago

1.0.52

10 years ago

1.0.51

10 years ago

1.0.50

10 years ago

1.0.49

10 years ago

1.0.48

10 years ago

1.0.47

10 years ago

1.0.46

10 years ago

1.0.45

10 years ago

1.0.44

10 years ago

1.0.43

10 years ago

1.0.42

10 years ago

1.0.41

10 years ago

1.0.40

10 years ago

1.0.39

10 years ago

1.0.38

10 years ago

1.0.37

10 years ago

1.0.36

10 years ago

1.0.35

10 years ago

1.0.34

10 years ago

1.0.33

10 years ago

1.0.32

10 years ago

1.0.31

10 years ago

1.0.30

10 years ago

1.0.29

10 years ago

1.0.28

10 years ago

1.0.27

10 years ago

1.0.26

10 years ago

1.0.25

10 years ago

1.0.24

10 years ago

1.0.23

10 years ago

1.0.22

10 years ago

1.0.21

10 years ago

1.0.20

10 years ago

1.0.19

10 years ago

1.0.18

10 years ago

1.0.17

10 years ago

1.0.16

10 years ago

1.0.15

10 years ago

1.0.14

10 years ago

1.0.13

10 years ago

1.0.12

10 years ago

1.0.11

10 years ago

1.0.10

10 years ago

1.0.9

10 years ago

1.0.8

10 years ago

1.0.7

10 years ago

1.0.6

10 years ago

1.0.5

10 years ago

1.0.4

10 years ago

1.0.3

10 years ago

1.0.2

10 years ago

1.0.1

10 years ago

1.0.0

10 years ago