├── .editorconfig
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── angular-test-runner.d.ts
├── angular-test-runner.js
├── karma.conf.js
├── package.json
├── src
├── actions.js
├── angular-test-runner.js
├── export.js
└── server-runner.js
└── test
├── debug-messages-test.js
├── http-server-test.js
├── internal
└── attach-to-dom-test.js
└── sample-test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 | charset = utf-8
8 |
9 | indent_style = space
10 | indent_size = 2
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | npm-debug.log
15 | node_modules
16 |
17 | .idea/
18 | *.iml
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*
2 | *.tgz
3 |
4 | test
5 |
6 | # WebStorm
7 | .idea
8 |
9 | .gitignore
10 | .npmignore
11 |
12 | node_modules
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.1.5 (7.12.2017)
2 |
3 | ### Allow to call `app.stop()` method without `attachToDocument: true`.
4 |
5 | ## 0.1.3 (16.11.2017)
6 |
7 | ### Wait as a last action in `perform` block
8 |
9 | We've fixed bug and now you can use `wait` operator as a last action in `perform` block
10 |
11 | ```typescript
12 | html.perform(
13 | click.in('.my-element'),
14 | wait(200)
15 | );
16 | ```
17 |
18 | ## 0.1.0 (31.10.2017)
19 |
20 | ### Attach to DOM
21 |
22 | If you want to test component which using for example `angular.element()` function (or library, eg. ngDialog)
23 | you can add `attachToDocument` configuration flag.
24 |
25 | ```typescript
26 | beforeEach(() => {
27 | app.run(['moduleName'], {attachToDocument : true});
28 | }
29 | ```
30 |
31 | Because we need to detach component from document after the test you need to call `app.stop()` function as well
32 |
33 | ```typescript
34 | afterEach(() => {
35 | app.stop();
36 | }
37 | ```
38 |
39 | [More complex example](https://github.com/Pragmatists/angular-test-runner/blob/master/test/sample-test.js#L242)
40 |
41 | ## 0.0.5 (27.10.2017)
42 |
43 | ### Blur action
44 |
45 | Now we can use blur action on element
46 |
47 | ```typescript
48 | html.perform(
49 | blur.from('input.name')
50 | );
51 | ```
52 |
53 |
54 | ## 0.0.1 (29.03.2017)
55 |
56 | ### Accept RegExp as http request url
57 |
58 | Now we can pass url to server http request as RegExp instead of String
59 |
60 | ```typescript
61 | server.get(new RegExp('/api/user/(\\d+)/address'), res => res.sendStatus(200));
62 | ```
63 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Pragmatists (http://pragmatists.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-test-runner
2 |
3 | [](https://gitter.im/angular-test-runner/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 | [](https://www.npmjs.com/package/angular-test-runner)
5 |
6 | Micro testing library that allows you to practice TDD in AngularJs applications.
7 |
8 | ## Installation
9 | To install it, type:
10 |
11 | $ npm install angular-test-runner --save-dev
12 |
13 | ## Configuration
14 | `angular-test-runner` has depedency on [`jasmine-jquery`](https://github.com/bessdsv/karma-jasmine-jquery) and [`karma-jasmine-jquery`](https://github.com/bessdsv/karma-jasmine-jquery) so it is required to add it as well.
15 |
16 | ```js
17 | // karma.conf.js
18 | module.exports = function(config) {
19 | config.set({
20 | frameworks: ['jasmine-jquery', 'jasmine'],
21 |
22 | plugins: ['karma-jasmine-jquery']
23 |
24 | // ...
25 | })
26 | }
27 | ```
28 | ## Replacement for ngMock
29 | **angular-test-runner** was created as a replacement of "official" Angular testing library: **ngMock**. It was designed to address following shortcommings of **ngMock**:
30 | 1. different style of testing for each component type (controller, directive, service, etc.),
31 | 2. long and boilerplate-rich setup of test,
32 | 3. difficult testing of html templates and no support for interacting with DOM elements,
33 | 4. focusing on testing objects in isolation instead of testing coherent set of behaviours (white-box testing)
34 | 5. [obscure tests with irrelevant informations](http://xunitpatterns.com/Obscure%20Test.html#Irrelevant%20Information) exposing implentation details, especially in context of HTTP communication (need of `$http.flush()`) and DOM interaction (need of `$scope.$digest()`)
35 |
36 | Therefore **angular-test-runner** tries to address those issues by:
37 | 1. providing uniform abstraction and consistent API for testing Angular parts, regardless of their type (contoller, directive, component, filter, etc.),
38 | 2. providing higher confidence by excercising html templates and interacting with real DOM elements (black-box testing),
39 | 3. promoting fine grained, self-encapsulated modules by enforcing testing at a module level instead of testing controllers, services, directives in isolation,
40 | 4. avioding mocks in favour of fakes for HTTP communication, use synchronous HTTP stubbing by default (no need for `$http.flush()`) and asynchronous on demand,
41 | 5. introducing compact, readable and declarative programming interface by introducing fluent interface.
42 |
43 |
44 | ## Example
45 |
46 | Following example presents tests for simple counter component.
47 |
48 | ``` javascript
49 | var test = require('angular-test-runner');
50 | var { click, expectElement } = test.actions;
51 |
52 | describe('counter component', () => {
53 |
54 | var app, server, html;
55 |
56 | beforeEach(() => {
57 | app = test.app(['counterApp']);
58 | server = test.http();
59 | });
60 |
61 | beforeEach(() => {
62 | html = app.runHtml('', {start: 0});
63 | });
64 |
65 | it('should render counter value', () => {
66 |
67 | html.verify(
68 | expectElement('#counter').toHaveText('0')
69 | );
70 |
71 | });
72 |
73 | it('should increment counter value by 1', () => {
74 |
75 | html.perform(
76 | click.in('button#increment')
77 | );
78 |
79 | html.verify(
80 | expectElement('#counter').toHaveText('1')
81 | );
82 | });
83 |
84 | it('should increment counter value by 1', () => {
85 |
86 | var jsonSentToServer;
87 |
88 | server.post('/counter/increment', (req) => {
89 | jsonSentToServer = req.body();
90 | req.sendStatus(200);
91 | });
92 |
93 | html.perform(
94 | click.in('button#increment')
95 | );
96 |
97 | html.verify(
98 | () => expect(jsonSentToServer).toEqual({ counter: 1 })
99 | );
100 | });
101 |
102 | });
103 | ```
104 |
105 | ### Comparision to ngMock
106 |
107 | Below you can compare **angular-test-runner** style of testing to traditional **ngMock** way.
108 | Following example was taken from [Official Angular Developers Guide](https://docs.angularjs.org/guide/component#unit-testing-component-controllers):
109 |
110 | 
111 |
112 | See more [examples](https://github.com/Pragmatists/angular-test-runner/blob/master/test/sample-test.js)
113 |
114 | ### Why not Protractor
115 |
116 | While **Protractor** is focused on end-to-end testing Angular application as a whole (from a browser perspective),
117 | **angular-test-runner** focuses on testing coherent parts of application by excerising selected components in isolation.
118 |
119 | Unlike **Protractor** in **angular-test-runner** you don't have to start your backend server nor host your html files on localhost so it is accessible to Selenium. Therefore test runner setup and configuration is much more simple.
120 | Moreover you are not forced to test entire pages at once, but you can exercise only a tiny fragments of your html (e.g. single directive, component or filter a.k.a. pipe).
121 |
122 | ## Features
123 | * readable performing [actions](https://github.com/Pragmatists/angular-test-runner/wiki/Testing-DOM-interactions), e.g. clicking on elements, typing into inputs etc.
124 | * easy request and response [server stubbing](https://github.com/Pragmatists/angular-test-runner/wiki/Testing-HTTP-interactions)
125 | * simplified testing of code with [async operations](https://github.com/Pragmatists/angular-test-runner/wiki/Testing-HTTP-interactions#async-mode)
126 | * easy to [write asserts](https://github.com/Pragmatists/angular-test-runner/wiki/Testing-DOM-interactions#expectelementelement) concerning html elements
127 |
--------------------------------------------------------------------------------
/angular-test-runner.d.ts:
--------------------------------------------------------------------------------
1 | declare interface AngularTestRunner {
2 | app: (modules: string[]) => angularTestRunner.ITestRunnerApp;
3 | http: (settings?: angularTestRunner.ITestRunnerHttpSettings) => angularTestRunner.ITestRunnerHttp;
4 | actions: angularTestRunner.ITestRunnerActions;
5 | }
6 |
7 | declare namespace angularTestRunner {
8 |
9 | interface ITestRunnerApp {
10 | stop: () => void;
11 | runHtml: (html: string, scope?: any) => ITestHtml;
12 | run: (location: string, scope?: any) => ITestHtml;
13 | }
14 |
15 | interface ITestRunnerHttpSettings {
16 | autoRespond?: boolean;
17 | respondImmediately?: boolean;
18 | respondAfter?: number;
19 | }
20 |
21 | interface ITestHtml {
22 | perform: (...actions: IAction[]) => void;
23 | verify: (...actions: IVerificationAction[]) => void;
24 | destroy: () => void;
25 | }
26 |
27 | interface IHttpRequest {
28 | body: () => any;
29 | query: () => any;
30 | header: (name: string) => string;
31 | sendJson: (json: any) => void;
32 | sendStatus: (status: number, json?: any) => void
33 | }
34 |
35 | type IHttpHandler = (request: IHttpRequest) => any;
36 | type IHttpEndpoint = (url: string | RegExp, handler: IHttpHandler) => any;
37 |
38 | interface ITestRunnerHttp {
39 |
40 | get: IHttpEndpoint;
41 | post: IHttpEndpoint;
42 | put: IHttpEndpoint;
43 | delete: IHttpEndpoint;
44 | stop: () => any;
45 | respond: () => any;
46 |
47 | }
48 |
49 | type IAction = (JQuery) => any;
50 |
51 | interface IAfterAction {
52 | after?: (number) => IAction;
53 | }
54 |
55 | type IVerificationAction = IAction & IAfterAction;
56 |
57 | interface IInAction {
58 | in: (string) => IAction & IAfterAction;
59 | }
60 |
61 | type IKeyAction = (key: number) => IAction & IInAction;
62 |
63 | interface ITestRunnerActions {
64 |
65 | click: IAction & IInAction;
66 | type: (text: string) => IAction & IInAction;
67 | keydown: IKeyAction;
68 | keypress: IKeyAction;
69 | keyup: IKeyAction;
70 | mouseover: IAction & IInAction;
71 | mouseleave: IAction & IInAction;
72 | wait: (delay: number) => IAction;
73 | apply: IAction;
74 | navigateTo: (url: string) => IAction;
75 | expectElement: (selector: string) => Matchers;
76 | listenTo: (eventName: string, handler: (data: any) => void) => IAction;
77 | publishEvent: (eventName: string, data: any) => IAction;
78 | }
79 |
80 | interface Matchers {
81 |
82 | /**
83 | * Check if DOM element has class.
84 | *
85 | * @param className Name of the class to check.
86 | *
87 | * @example
88 | * // returns true
89 | * expect($('
')).toHaveClass("some-class")
90 | */
91 | toHaveClass(className: string): IVerificationAction;
92 |
93 | /**
94 | * Check if DOM element has the given CSS properties.
95 | *
96 | * @param css Object containing the properties (and values) to check.
97 | *
98 | * @example
99 | * // returns true
100 | * expect($('')).toHaveCss({display: "none", margin: "10px"})
101 | *
102 | * @example
103 | * // returns true
104 | * expect($('')).toHaveCss({margin: "10px"})
105 | */
106 | toHaveCss(css: any): IVerificationAction;
107 |
108 | /**
109 | * Checks if DOM element is visible.
110 | * Elements are considered visible if they consume space in the document. Visible elements have a width or height that is greater than zero.
111 | */
112 | toBeVisible(): IVerificationAction;
113 | /**
114 | * Check if DOM element is hidden.
115 | * Elements can be hidden for several reasons:
116 | * - They have a CSS display value of none ;
117 | * - They are form elements with type equal to hidden.
118 | * - Their width and height are explicitly set to 0.
119 | * - An ancestor element is hidden, so the element is not shown on the page.
120 | */
121 | toBeHidden(): IVerificationAction;
122 |
123 | /**
124 | * Only for tags that have checked attribute
125 | *
126 | * @example
127 | * // returns true
128 | * expect($('')).toBeSelected()
129 | */
130 | toBeSelected(): IVerificationAction;
131 |
132 | /**
133 | * Only for tags that have checked attribute
134 | * @example
135 | * // returns true
136 | * expect($('')).toBeChecked()
137 | */
138 | toBeChecked(): IVerificationAction;
139 |
140 | /**
141 | * Checks for child DOM elements or text
142 | */
143 | toBeEmpty(): IVerificationAction;
144 |
145 | /**
146 | * Checks if element exists in or out the DOM.
147 | */
148 | toExist(): IVerificationAction;
149 |
150 | /**
151 | * Checks if array has the given length.
152 | *
153 | * @param length Expected length
154 | */
155 | toHaveLength(length: number): IVerificationAction;
156 |
157 | /**
158 | * Check if DOM element contains an attribute and, optionally, if the value of the attribute is equal to the expected one.
159 | *
160 | * @param attributeName Name of the attribute to check
161 | * @param expectedAttributeValue Expected attribute value
162 | */
163 | toHaveAttr(attributeName: string, expectedAttributeValue?: any): IVerificationAction;
164 |
165 | /**
166 | * Check if DOM element contains a property and, optionally, if the value of the property is equal to the expected one.
167 | *
168 | * @param propertyName Property name to check
169 | * @param expectedPropertyValue Expected property value
170 | */
171 | toHaveProp(propertyName: string, expectedPropertyValue?: any): IVerificationAction;
172 |
173 | /**
174 | * Check if DOM element has the given Id
175 | *
176 | * @param Id Expected identifier
177 | */
178 | toHaveId(id: string): IVerificationAction;
179 |
180 | /**
181 | * Check if DOM element has the specified HTML.
182 | *
183 | * @example
184 | * // returns true
185 | * expect($('
')).toContainText('header')
213 | */
214 | toContainText(text: string): IVerificationAction;
215 |
216 | /**
217 | * Check if DOM element has the given value.
218 | * This can only be applied for element on with jQuery val() can be called.
219 | *
220 | * @example
221 | * // returns true
222 | * expect($('')).toHaveValue('some text')
223 | */
224 | toHaveValue(value: string): IVerificationAction;
225 |
226 | /**
227 | * Check if DOM element has the given data.
228 | * This can only be applied for element on with jQuery data(key) can be called.
229 | *
230 | */
231 | toHaveData(key: string, expectedValue: string): IVerificationAction;
232 | toBe(selector: JQuery): IVerificationAction;
233 |
234 | /**
235 | * Check if DOM element is matched by the given selector.
236 | *
237 | * @example
238 | * // returns true
239 | * expect($('
')).toContain('some-class')
240 | */
241 | toContain(selector: JQuery): IVerificationAction;
242 |
243 | /**
244 | * Check if DOM element exists inside the given parent element.
245 | *
246 | * @example
247 | * // returns true
248 | * expect($('