Comparing version 1.1.0 to 1.2.0
@@ -5,20 +5,22 @@ # Contributing to alsatian | ||
## First of thanks | ||
## First off - thanks | ||
Social coding is great so thank you for being a part of it! Hats off to you!!! | ||
## Asking for help | ||
## The process | ||
Everybody needs help sometimes and we are completely open to answering all and any questions you may have before, during or after contributing or even if you aren't contributing and just have a question. Just raise an issue and we'll gladly get back to you. | ||
### 1. Issue | ||
## Before you create a pull request | ||
If there's no issue that covers the new feature or bug you'd like to fix then create one. Otherwise if noone is assigned to the existing issue just let us know you'd like to work on it. | ||
All you need to do is complete the following checklist | ||
### 2. Do your thing | ||
* Create an issue for the pull request | ||
* Add JSDocs if relevant | ||
* Write tests in order to prove the code works | ||
Get your creative juices flowing, work however you want - there should be plenty of tools in place to help you along the way. | ||
## Creating a pull request | ||
### 3. Pull request | ||
Now you're ready to create a pull request go ahead, all you'll need to do is link it to the issue you created earlier and your done. You can also of course add any further information you'd like but otherwise you're done! | ||
There's a checklist whenever you create your pull request that'll help you check everything is in order before submitting. After you create a pull request our automatic CI checks will inspect your code and flag any issues if there are any. Then an Alsatian member will review the code and OK it to get merged and your wonderful code will be available in the next Alsatian release :) | ||
## Asking for help | ||
Everybody needs help sometimes and we are completely open to answering any and all questions you may have before, during or after contributing or even if you aren't contributing and just have a question. Just raise an issue or tag us in a comment and we'll gladly get back to you. |
import { Expect, Matcher, TestOutputStream, TestSet } from "./"; | ||
import { AsyncSetup, AsyncTeardown, AsyncTest, FocusTest, FocusTests, IgnoreTest, IgnoreTests, Setup, Teardown, Test, TestCase, TestFixture, Timeout } from "./decorators"; | ||
import { AsyncSetup, AsyncSetupFixture, AsyncTeardown, AsyncTeardownFixture, AsyncTest, FocusTest, FocusTests, IgnoreTest, IgnoreTests, Setup, SetupFixture, Teardown, TeardownFixture, Test, TestCase, TestFixture, Timeout } from "./decorators"; | ||
import { Any, FunctionSpy, SpyOn, SpyOnProperty } from "./spying"; | ||
@@ -8,2 +8,2 @@ import { TestCaseResult, TestFixtureResults, TestOutcome, TestResults, TestSetResults } from "./results"; | ||
import { TestRunner } from "./running"; | ||
export { Any, AsyncSetup, AsyncTeardown, AsyncTest, Expect, FocusTest, FocusTests, IgnoreTest, IgnoreTests, Matcher, Setup, FunctionSpy, SpyOn, SpyOnProperty, Teardown, Test, TestCase, TestRunner, TestSet, Timeout, TestSetResults, TestOutcome, METADATA_KEYS, TestFixture, TestOutputStream, MatchError, TestFixtureResults, TestResults, TestCaseResult, TestTimeoutError }; | ||
export { Any, AsyncSetup, AsyncSetupFixture, AsyncTeardown, AsyncTeardownFixture, AsyncTest, Expect, FocusTest, FocusTests, IgnoreTest, IgnoreTests, Matcher, Setup, SetupFixture, FunctionSpy, SpyOn, SpyOnProperty, Teardown, TeardownFixture, Test, TestCase, TestRunner, TestSet, Timeout, TestSetResults, TestOutcome, METADATA_KEYS, TestFixture, TestOutputStream, MatchError, TestFixtureResults, TestResults, TestCaseResult, TestTimeoutError }; |
@@ -9,3 +9,5 @@ "use strict"; | ||
exports.AsyncSetup = decorators_1.AsyncSetup; | ||
exports.AsyncSetupFixture = decorators_1.AsyncSetupFixture; | ||
exports.AsyncTeardown = decorators_1.AsyncTeardown; | ||
exports.AsyncTeardownFixture = decorators_1.AsyncTeardownFixture; | ||
exports.AsyncTest = decorators_1.AsyncTest; | ||
@@ -17,3 +19,5 @@ exports.FocusTest = decorators_1.FocusTest; | ||
exports.Setup = decorators_1.Setup; | ||
exports.SetupFixture = decorators_1.SetupFixture; | ||
exports.Teardown = decorators_1.Teardown; | ||
exports.TeardownFixture = decorators_1.TeardownFixture; | ||
exports.Test = decorators_1.Test; | ||
@@ -20,0 +24,0 @@ exports.TestCase = decorators_1.TestCase; |
@@ -7,5 +7,7 @@ declare const TEST_FIXTURE: "alsatian:test-fixture"; | ||
declare const SETUP: "alsatian:setup"; | ||
declare const SETUP_FIXTURE: "alsatian:setup-fixture"; | ||
declare const TEARDOWN: "alsatian:teardown"; | ||
declare const TEARDOWN_FIXTURE: "alsatian:teardown-fixture"; | ||
declare const TEST_CASES: "alsatian:testcases"; | ||
declare const TIMEOUT: "alsatian:timeout"; | ||
export { TEST_FIXTURE, TESTS, FOCUS, IGNORE, IGNORE_REASON, SETUP, TEARDOWN, TEST_CASES, TIMEOUT }; | ||
export { TEST_FIXTURE, TESTS, FOCUS, IGNORE, IGNORE_REASON, SETUP, SETUP_FIXTURE, TEARDOWN, TEARDOWN_FIXTURE, TEST_CASES, TIMEOUT }; |
@@ -20,4 +20,8 @@ // currently typing these values to string as literal causes | ||
exports.SETUP = SETUP; | ||
var SETUP_FIXTURE = "alsatian:setup-fixture"; | ||
exports.SETUP_FIXTURE = SETUP_FIXTURE; | ||
var TEARDOWN = "alsatian:teardown"; | ||
exports.TEARDOWN = TEARDOWN; | ||
var TEARDOWN_FIXTURE = "alsatian:teardown-fixture"; | ||
exports.TEARDOWN_FIXTURE = TEARDOWN_FIXTURE; | ||
var TEST_CASES = "alsatian:testcases"; | ||
@@ -24,0 +28,0 @@ exports.TEST_CASES = TEST_CASES; |
import { AsyncSetup } from "./async-setup-decorator"; | ||
import { AsyncSetupFixture } from "./async-setup-fixture-decorator"; | ||
import { AsyncTeardown } from "./async-teardown-decorator"; | ||
import { AsyncTeardownFixture } from "./async-teardown-fixture-decorator"; | ||
import { AsyncTest } from "./async-test-decorator"; | ||
@@ -9,3 +11,5 @@ import { FocusTest } from "./focus-test-decorator"; | ||
import { Setup } from "./setup-decorator"; | ||
import { SetupFixture } from "./setup-fixture-decorator"; | ||
import { Teardown } from "./teardown-decorator"; | ||
import { TeardownFixture } from "./teardown-fixture-decorator"; | ||
import { TestCase } from "./test-case-decorator"; | ||
@@ -15,2 +19,2 @@ import { Test } from "./test-decorator"; | ||
import { Timeout } from "./timeout-decorator"; | ||
export { AsyncSetup, AsyncTeardown, AsyncTest, FocusTest, FocusTests, IgnoreTest, IgnoreTests, Setup, Teardown, TestFixture, Test, TestCase, Timeout }; | ||
export { AsyncSetup, AsyncSetupFixture, AsyncTeardown, AsyncTeardownFixture, AsyncTest, FocusTest, FocusTests, IgnoreTest, IgnoreTests, Setup, SetupFixture, Teardown, TeardownFixture, TestFixture, Test, TestCase, Timeout }; |
"use strict"; | ||
var async_setup_decorator_1 = require("./async-setup-decorator"); | ||
exports.AsyncSetup = async_setup_decorator_1.AsyncSetup; | ||
var async_setup_fixture_decorator_1 = require("./async-setup-fixture-decorator"); | ||
exports.AsyncSetupFixture = async_setup_fixture_decorator_1.AsyncSetupFixture; | ||
var async_teardown_decorator_1 = require("./async-teardown-decorator"); | ||
exports.AsyncTeardown = async_teardown_decorator_1.AsyncTeardown; | ||
var async_teardown_fixture_decorator_1 = require("./async-teardown-fixture-decorator"); | ||
exports.AsyncTeardownFixture = async_teardown_fixture_decorator_1.AsyncTeardownFixture; | ||
var async_test_decorator_1 = require("./async-test-decorator"); | ||
@@ -18,4 +22,8 @@ exports.AsyncTest = async_test_decorator_1.AsyncTest; | ||
exports.Setup = setup_decorator_1.Setup; | ||
var setup_fixture_decorator_1 = require("./setup-fixture-decorator"); | ||
exports.SetupFixture = setup_fixture_decorator_1.SetupFixture; | ||
var teardown_decorator_1 = require("./teardown-decorator"); | ||
exports.Teardown = teardown_decorator_1.Teardown; | ||
var teardown_fixture_decorator_1 = require("./teardown-fixture-decorator"); | ||
exports.TeardownFixture = teardown_fixture_decorator_1.TeardownFixture; | ||
var test_case_decorator_1 = require("./test-case-decorator"); | ||
@@ -22,0 +30,0 @@ exports.TestCase = test_case_decorator_1.TestCase; |
import { ContentsMatchError } from "./contents-match-error"; | ||
import { EmptyMatchError } from "./empty-match-error"; | ||
import { EqualMatchError } from "./equal-match-error"; | ||
@@ -14,2 +15,2 @@ import { ErrorMatchError } from "./error-match-error"; | ||
import { TruthyMatchError } from "./truthy-match-error"; | ||
export { MatchError, ExactMatchError, EqualMatchError, RegexMatchError, TruthyMatchError, ContentsMatchError, LessThanMatchError, GreaterThanMatchError, ErrorMatchError, FunctionCallMatchError, FunctionCallCountMatchError, TestTimeoutError, PropertySetMatchError }; | ||
export { MatchError, ExactMatchError, EqualMatchError, RegexMatchError, TruthyMatchError, ContentsMatchError, LessThanMatchError, GreaterThanMatchError, ErrorMatchError, FunctionCallMatchError, FunctionCallCountMatchError, TestTimeoutError, PropertySetMatchError, EmptyMatchError }; |
"use strict"; | ||
var contents_match_error_1 = require("./contents-match-error"); | ||
exports.ContentsMatchError = contents_match_error_1.ContentsMatchError; | ||
var empty_match_error_1 = require("./empty-match-error"); | ||
exports.EmptyMatchError = empty_match_error_1.EmptyMatchError; | ||
var equal_match_error_1 = require("./equal-match-error"); | ||
@@ -5,0 +7,0 @@ exports.EqualMatchError = equal_match_error_1.EqualMatchError; |
@@ -63,2 +63,6 @@ import { FunctionSpyMatcher } from "./matchers"; | ||
/** | ||
* Checks that an array is empty, a string is empty, or an object literal has no properties | ||
*/ | ||
toBeEmpty(): void; | ||
/** | ||
* Checks that a function throws an error when executed | ||
@@ -65,0 +69,0 @@ */ |
@@ -153,2 +153,23 @@ "use strict"; | ||
/** | ||
* Checks that an array is empty, a string is empty, or an object literal has no properties | ||
*/ | ||
Matcher.prototype.toBeEmpty = function () { | ||
if (null === this.actualValue || undefined === this.actualValue) { | ||
throw new TypeError("toBeEmpty requires value passed in to Expect not to be null or undefined"); | ||
} | ||
if (typeof this.actualValue === "string" || Array.isArray(this.actualValue)) { | ||
if ((this.actualValue.length === 0) !== this.shouldMatch) { | ||
throw new errors_1.EmptyMatchError(this.actualValue, this.shouldMatch); | ||
} | ||
} | ||
else if (this.actualValue.constructor === Object) { | ||
if ((Object.keys(this.actualValue).length === 0) !== this.shouldMatch) { | ||
throw new errors_1.EmptyMatchError(this.actualValue, this.shouldMatch); | ||
} | ||
} | ||
else { | ||
throw new TypeError("toBeEmpty requires value passed in to Expect to be an array, string or object literal"); | ||
} | ||
}; | ||
/** | ||
* Checks that a function throws an error when executed | ||
@@ -155,0 +176,0 @@ */ |
@@ -77,2 +77,3 @@ "use strict"; | ||
return __awaiter(this, void 0, void 0, function () { | ||
var error_1; | ||
return __generator(this, function (_a) { | ||
@@ -86,10 +87,19 @@ switch (_a.label) { | ||
_a.sent(); | ||
_a.label = 3; | ||
case 3: | ||
_a.trys.push([3, 6, , 8]); | ||
return [4 /*yield*/, this._runTest(this._test.timeout || timeout)]; | ||
case 3: | ||
case 4: | ||
_a.sent(); | ||
return [4 /*yield*/, this._teardown()]; | ||
case 4: | ||
case 5: | ||
_a.sent(); | ||
_a.label = 5; | ||
case 5: return [2 /*return*/]; | ||
return [3 /*break*/, 8]; | ||
case 6: | ||
error_1 = _a.sent(); | ||
return [4 /*yield*/, this._teardown()]; | ||
case 7: | ||
_a.sent(); | ||
throw error_1; | ||
case 8: return [2 /*return*/]; | ||
} | ||
@@ -96,0 +106,0 @@ }); |
@@ -15,2 +15,5 @@ import "reflect-metadata"; | ||
private _runTests(testSetRunInfo, results); | ||
private _setupFixture(fixture); | ||
private _teardownFixture(fixture); | ||
private _runFixtureFunctions(fixture, metadataKey); | ||
} |
@@ -92,3 +92,3 @@ "use strict"; | ||
return __awaiter(this, void 0, void 0, function () { | ||
var currentTestFixtureResults, currentTestResults, errorOccurredRunningTest, _loop_1, arguments_1, this_1, _i, _a, testItem; | ||
var currentTestFixtureResults, currentTestResults, errorOccurredRunningTest, _loop_1, arguments_1, this_1, _i, _a, testItem, lastTestItem; | ||
return __generator(this, function (_b) { | ||
@@ -104,7 +104,15 @@ switch (_b.label) { | ||
previousTestItem = testSetRunInfo.testPlan.testItems[testItemIndex - 1]; | ||
// if new fixture | ||
if (!previousTestItem || previousTestItem.testFixture !== testItem.testFixture) { | ||
this_1._outputStream.emitFixture(testItem.testFixture); | ||
currentTestFixtureResults = results.addTestFixtureResult(testItem.testFixture); | ||
} | ||
if (!(!previousTestItem || previousTestItem.testFixture !== testItem.testFixture)) return [3 /*break*/, 4]; | ||
if (!previousTestItem) return [3 /*break*/, 2]; | ||
return [4 /*yield*/, this_1._teardownFixture(previousTestItem.testFixture.fixture)]; | ||
case 1: | ||
_a.sent(); | ||
_a.label = 2; | ||
case 2: return [4 /*yield*/, this_1._setupFixture(testItem.testFixture.fixture)]; | ||
case 3: | ||
_a.sent(); | ||
this_1._outputStream.emitFixture(testItem.testFixture); | ||
currentTestFixtureResults = results.addTestFixtureResult(testItem.testFixture); | ||
_a.label = 4; | ||
case 4: | ||
// if new test | ||
@@ -114,17 +122,17 @@ if (!previousTestItem || previousTestItem.test !== testItem.test) { | ||
} | ||
_a.label = 1; | ||
case 1: | ||
_a.trys.push([1, 3, , 4]); | ||
_a.label = 5; | ||
case 5: | ||
_a.trys.push([5, 7, , 8]); | ||
return [4 /*yield*/, testItem.run(testSetRunInfo.timeout)]; | ||
case 2: | ||
case 6: | ||
_a.sent(); | ||
result = currentTestResults.addTestCaseResult(testItem.testCase.arguments_1); | ||
errorOccurredRunningTest = null; | ||
return [3 /*break*/, 4]; | ||
case 3: | ||
return [3 /*break*/, 8]; | ||
case 7: | ||
error_1 = _a.sent(); | ||
result = currentTestResults.addTestCaseResult(testItem.testCase.arguments_1, error_1); | ||
errorOccurredRunningTest = error_1; | ||
return [3 /*break*/, 4]; | ||
case 4: | ||
return [3 /*break*/, 8]; | ||
case 8: | ||
// emit onComplete event out of Alsatian if call back has been defined | ||
@@ -162,2 +170,6 @@ if (this_1._onTestCompleteCBs) { | ||
case 4: | ||
lastTestItem = testSetRunInfo.testPlan.testItems[testSetRunInfo.testPlan.testItems.length - 1]; | ||
return [4 /*yield*/, this._teardownFixture(lastTestItem.testFixture.fixture)]; | ||
case 5: | ||
_b.sent(); | ||
this._outputStream.end(); | ||
@@ -169,2 +181,55 @@ return [2 /*return*/]; | ||
}; | ||
TestRunner.prototype._setupFixture = function (fixture) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, this._runFixtureFunctions(fixture, alsatian_core_1.METADATA_KEYS.SETUP_FIXTURE)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
TestRunner.prototype._teardownFixture = function (fixture) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, this._runFixtureFunctions(fixture, alsatian_core_1.METADATA_KEYS.TEARDOWN_FIXTURE)]; | ||
case 1: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
TestRunner.prototype._runFixtureFunctions = function (fixture, metadataKey) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var fixtureFunctions, _i, fixtureFunctions_1, fixtureFunction; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
fixtureFunctions = Reflect.getMetadata(metadataKey, fixture); | ||
if (!fixtureFunctions) return [3 /*break*/, 5]; | ||
_i = 0, fixtureFunctions_1 = fixtureFunctions; | ||
_a.label = 1; | ||
case 1: | ||
if (!(_i < fixtureFunctions_1.length)) return [3 /*break*/, 5]; | ||
fixtureFunction = fixtureFunctions_1[_i]; | ||
if (!fixtureFunction.isAsync) return [3 /*break*/, 3]; | ||
return [4 /*yield*/, fixture[fixtureFunction.propertyKey].call(fixture)]; | ||
case 2: | ||
_a.sent(); | ||
return [3 /*break*/, 4]; | ||
case 3: | ||
fixture[fixtureFunction.propertyKey].call(fixture); | ||
_a.label = 4; | ||
case 4: | ||
_i++; | ||
return [3 /*break*/, 1]; | ||
case 5: return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
return TestRunner; | ||
@@ -171,0 +236,0 @@ }()); |
@@ -33,5 +33,5 @@ "use strict"; | ||
TestLoader.prototype._loadTestFixture = function (testFixtureConstructor, defaultFixtureDescription) { | ||
// get test fixture metadata or create new metadata | ||
// to support not requiring the TestFixture decorator. | ||
// This functionality will be removed in 2.0.0 where | ||
// get test fixture metadata or create new metadata | ||
// to support not requiring the TestFixture decorator. | ||
// This functionality will be removed in 2.0.0 where | ||
// TestFixture decorator will become mandatory | ||
@@ -38,0 +38,0 @@ var testFixture = Reflect.getMetadata(alsatian_core_1.METADATA_KEYS.TEST_FIXTURE, testFixtureConstructor) |
{ | ||
"name": "alsatian", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "TypeScript and JavaScript testing framework for beautiful and readable tests", | ||
@@ -5,0 +5,0 @@ "author": "James Richford <=> (=)", |
815
README.md
<p id="banner" align="center"> | ||
<img src="https://github.com/alsatian-test/alsatian/raw/master/documentation/images/alsatian-mascot-logo.png" alt="Alsatian Mascot Logo" /> | ||
<p id="tag-line" align="center">Awesomely easy and useful TypeScript and JavaScript testing framework with test cases, compatible with <a href="https://github.com/alsatian-test/alsatian/wiki/using-alsatian-with-selenium">selenium</a>, istanbul and tap reporters.</p> | ||
<p id="tag-line" align="center">Awesomely easy and useful TypeScript and JavaScript testing framework with test cases, compatible with <a href="https://github.com/alsatian-test/alsatian/wiki/using-alsatian-with-selenium">selenium</a>, <a href="https://github.com/alsatian-test/alsatian/wiki/check-test-coverage-with-nyc">coverage checkers</a> and <a href="https://github.com/alsatian-test/alsatian/wiki/using-alsatian-with-tap-reporters">TAP reporters</a>.</p> | ||
</p> | ||
@@ -35,790 +35,77 @@ <p id="badges" align="center"> | ||
## Why would I use Alsatian? | ||
The key question! Well Alsatian has a lot going for it here are just a few great things to note: | ||
## Quick Start | ||
* All the awesome features you love from existing frameworks | ||
* TestCase decorator allows you to write smaller, DRY and more readable tests | ||
* No globals! | ||
* TAP support so you can use your favourite TAP reporter | ||
* Great CI process, every pull request and push on every branch is scrutinised to ensure high quality | ||
* 100% coverage all statements, lines, branches are covered in Alsatian tests | ||
* Various services rate us very highly on lots of different factors, check out our badges | ||
* Everything is documented in a friendly and simple way to help you get to the unit test setup of your dreams | ||
* Being written in TypeScript it fits perfectly into your TypeScript but still compatible with JavaScript too! | ||
* Active support if you've got a question, a suggestion or found an issue let us know and we'll get back to you quickly | ||
If you're loving TypeScript then pop on down to our [TypeScript Quick Start](https://github.com/alsatian-test/alsatian/wiki/typescript-setup). | ||
Also it's lightning fast, watch it run all of it's unit tests in super quick time! | ||
![Alsatian Test Run Video](https://github.com/alsatian-test/alsatian/raw/master/documentation/images/alsatian-test-run.gif) | ||
Otherwise if you're more of a JavaScript kinda person, have a gander at our [JavaScript Quick Start](https://github.com/alsatian-test/alsatian/wiki/javascript-setup) | ||
## Installing | ||
Full documentation can be found on our [wiki](https://github.com/alsatian-test/alsatian/wiki/) the examples are usually in TypeScript but should be pretty much the same code for JavaScript (just lose the access modifiers and types). | ||
Good news everybody, we're on NPM. | ||
``` | ||
npm install alsatian | ||
``` | ||
## Alsatian is different | ||
## Use with JavaScript | ||
Using a different approach than other JavaScript test frameworks allows us to use more powerful patterns. | ||
If you're using JavaScript, no worries you can still use Alsatian with Babel. Currently there is no official support for decorators (see [babel issue](https://github.com/babel/babel#2645)) but you can use a plugin! | ||
Add `transform-decorators-legacy` plugin | ||
``` | ||
npm install babel-plugin-transform-decorators-legacy --save-dev | ||
``` | ||
Then update your `.babelrc` | ||
``` | ||
{ | ||
... | ||
"plugins": ["transform-decorators-legacy"] | ||
... | ||
} | ||
``` | ||
You should now be able to use Alsatian decorators as in all the examples below, Hooray! | ||
## Running alsatian | ||
### CLI | ||
Alsatian has a CLI for easy use with your package.json or your favourite cli tool | ||
``` | ||
alsatian [list of globs] | ||
alsatian "./test/**/*.spec.js" "./special-test.js" | ||
``` | ||
#### CLI Options | ||
You can change how Alsatian runs your tests using the available options | ||
| Option | Alias | Description | | ||
| ------------------------- | ------ | ---------------------------------------------------------------------------- | | ||
| --help | -h | Outputs info about how to use the CLI | | ||
| --version | -v | Outputs the version of the CLI | | ||
| --tap | -T | Will make Alsatian output in TAP format (to be consumed by a TAP reporter) | | ||
| --timeout [number in ms] | -t | Sets the maximum time that a test can run for before failing (default 500ms) | | ||
### Node.js | ||
If you're more of a nodey person then you can use that too | ||
```typescript | ||
import { TestSet, TestRunner } from "alsatian"; | ||
import { TapBark } from "tap-bark"; | ||
// no globals and typing support out of the box with intellisense | ||
import { AsyncTest, Expect, Test, TestCase, TestFixture } from "alsatian"; | ||
// create test set | ||
const testSet = TestSet.create(); | ||
@TestFixture("whatever you'd like to call the fixture") | ||
export class SetOfTests { | ||
// use the async/await pattern in your tests as you would in your code | ||
@AsyncTest("asychronous test") | ||
public async asyncTest() { | ||
const response = await somethingToHappen(); | ||
// add your tests | ||
testSet.addTestsFromFiles("./tests/you-want/to-add/**/*.spec.js"); | ||
Expect(response).toBeDefined(); | ||
} | ||
// create a test runner | ||
const testRunner = new TestRunner(); | ||
// setup the output | ||
testRunner.outputStream | ||
// this will use alsatian's default output if you remove this | ||
// you'll get TAP or you can add your favourite TAP reporter in it's place | ||
.pipe(TapBark.create().getPipeable()) | ||
// pipe to the console | ||
.pipe(process.stdout); | ||
// run the test set | ||
testRunner.run(testSet) | ||
// this will be called after all tests have been run | ||
.then((results) => done()) | ||
// this will be called if there was a problem | ||
.catch((error) => doSomethingWith(error)); | ||
``` | ||
### Gulp | ||
If instead you prefer to gulp it up you can write a task similar to how you'd work with Node.js | ||
```typescript | ||
import * as Gulp from "gulp"; | ||
import { TestSet, TestRunner } from "alsatian"; | ||
import { TapBark } from "tap-bark"; | ||
Gulp.task("test", (done: () => any) => { | ||
// create test set | ||
const testSet = TestSet.create(); | ||
// add your tests | ||
testSet.addTestsFromFiles("./tests/you-want/to-add/**/*.spec.js"); | ||
// create a test runner | ||
const testRunner = new TestRunner(); | ||
// setup the output | ||
testRunner.outputStream | ||
// this will use alsatian's default output if you remove this | ||
// you'll get TAP or you can add your favourite TAP reporter in it's place | ||
.pipe(TapBark.create().getPipeable()) | ||
// pipe to the console | ||
.pipe(process.stdout); | ||
// run the test set | ||
testRunner.run(testSet) | ||
// and tell gulp when we're done | ||
.then(() => done()); | ||
}); | ||
``` | ||
## Using alsatian | ||
Create your first spec file | ||
```typescript | ||
import { Expect, Test } from "alsatian"; | ||
export class ExampleTestFixture { | ||
@Test() | ||
public exampleTest() { | ||
Expect(1 + 1).toBe(2); | ||
} | ||
} | ||
``` | ||
Then check all is well | ||
``` | ||
> alsatian "./path/to/example.spec.js" | ||
TAP version 13 | ||
1..1 | ||
ok 1 - exampleTest | ||
``` | ||
### Naming Tests | ||
By default, tests will be named the same as their functions and fixtures will be named the same as their class. This will be what is output by alsatian. However, you can give the test or fixture more meaningful name simply by supplying the ```Test``` and ```TestFixture``` annotations with whatever you desire. | ||
```typescript | ||
import { Expect, Test, TestFixture } from "alsatian"; | ||
@TestFixture("Awesome Test Fixture") | ||
export class ExampleTestFixture { | ||
@Test("Confirm 1 + 1 is 2") | ||
public test1() { | ||
Expect(1 + 1).toBe(2); | ||
} | ||
} | ||
``` | ||
Then check all is well | ||
``` | ||
> alsatian ./path/to/example.spec | ||
Awesome Test Fixture | ||
Confirm 1 + 1 is 2 | ||
|====================| | ||
Pass: 1/1 | ||
Fail: 0/1 | ||
Ignore: 0/1 | ||
``` | ||
### Test Cases | ||
You can pass arguments to your tests simply by using the ```TestCase``` annotation | ||
```typescript | ||
import { Expect, TestCase, TestFixture } from "alsatian"; | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@TestCase(1, 2) | ||
@TestCase(4, 5) | ||
public exampleTest(preIteratedValue: number, expected: number) { | ||
Expect(preIteratedValue++).toBe(expected); | ||
} | ||
} | ||
``` | ||
### Matchers | ||
Now you've set up some tests, it's time to check your code is working. Let's start easy. | ||
#### toBe | ||
To be or not to be, that is the question! Simply put this checks whether actual === expected | ||
```typescript | ||
Expect(1 + 1).toBe(2); | ||
Expect(1 + 1).not.toBe(3); | ||
``` | ||
#### toEqual | ||
Next we can check if it's pretty much the same actual == expected | ||
```typescript | ||
Expect("1").toEqual(1); | ||
Expect(1 + 1).not.toEqual("3"); | ||
``` | ||
#### toMatch | ||
Now a cheeky little regular expression if you don't mind | ||
```typescript | ||
Expect("something").toMatch(/some/); | ||
Expect("another thing").not.toMatch(/something/); | ||
``` | ||
#### toBeDefined | ||
Is it there or not? actual !== undefined | ||
```typescript | ||
Expect("something").toBeDefined(); | ||
Expect(undefined).not.toBeDefined(); | ||
``` | ||
#### toBeNull | ||
Is it something or not? actual === null | ||
```typescript | ||
Expect(null).toBeNull(); | ||
Expect("something").not.toBeNull(); | ||
``` | ||
#### toBeTruthy | ||
Is it trueish? actual == trueish | ||
```typescript | ||
Expect(1).toBeTruthy(); | ||
Expect(0).not.toBeTruthy(); | ||
``` | ||
#### toContain | ||
Does the string contain another string or an array contain an item? | ||
```typescript | ||
Expect("something").toContain("thing"); | ||
Expect([1, 2, 3]).toContain(2); | ||
Expect("another thing").not.toContain("something"); | ||
Expect([1, 2, 3]).not.toContain(4); | ||
``` | ||
#### toBeGreaterThan | ||
Which one's larger (hopefully the actual) | ||
```typescript | ||
Expect(2).toBeGreaterThan(1); | ||
Expect(1).not.toBeGreaterThan(2); | ||
``` | ||
#### toBeLessThan | ||
For when you don't want things to get out of control, check it's not too big | ||
```typescript | ||
Expect(1).toBeLessThan(2); | ||
Expect(2).not.toBeLessThan(1); | ||
``` | ||
#### toThrow | ||
Check whether a function throws an error | ||
```typescript | ||
Expect(() => throw new Error()).toThrow(); | ||
Expect(() => {}).not.toThrow(); | ||
``` | ||
#### toThrowError | ||
Check whether a function throws a specific error with a given message | ||
```typescript | ||
Expect(() => throw new TypeError("things went wrong")).toThrowError(TypeError, "things went wrong"); | ||
Expect(() => throw new Error("some error we don't care about")).not.toThrow(TypeError, "super nasty error"); | ||
``` | ||
### Spying | ||
When we want to check functions are called, this is simple first we need to turn it into a spy... | ||
```typescript | ||
import { SpyOn } from "alsatian"; | ||
let some = { | ||
function: () => {} | ||
}; | ||
SpyOn(some, "function"); | ||
``` | ||
... then check it's been called ... | ||
```typescript | ||
Expect(some.function).toHaveBeenCalled(); | ||
``` | ||
... or check it's been called with certain arguments ... | ||
```typescript | ||
Expect(some.function).toHaveBeenCalledWith(this, "and that"); | ||
``` | ||
... or any arguments ... | ||
```typescript | ||
// you can use the Any function to signify an argument can be anything or any specific type | ||
Expect(some.function).toHaveBeenCalledWith(Any, Any(Number), Any(String)); | ||
``` | ||
... or a specific number of times ... | ||
```typescript | ||
Expect(some.function).toHaveBeenCalled().exactly(42).times; | ||
Expect(some.function).toHaveBeenCalledWith("something").anythingBut(10).times; | ||
Expect(some.function).toHaveBeenCalledWith(Any).lessThan(5).times; | ||
Expect(some.function).toHaveBeenCalledWith(Any(Number), Any(Array)).greaterThan(2).times; | ||
// Note that this functionality must not be used with the not operator | ||
// e.g. the following throws an error | ||
Expect(some.function).not.toHaveBeenCalled().lessThan(42).times; | ||
// this should be written | ||
Expect(some.function).toHaveBeenCalled().greaterThan(41).times; | ||
``` | ||
... you can stub it out ... | ||
```typescript | ||
SpyOn(some, "function").andStub(); | ||
``` | ||
... you can make it call something else ... | ||
```typescript | ||
SpyOn(some, "function").andCall(() => { | ||
console.log("I are called"); // make it do whatever you like | ||
return "whatever you like"; // and also return stuff too! | ||
}); | ||
``` | ||
... or make it return whatever you desire ... | ||
```typescript | ||
SpyOn(some, "function").andReturn(42); | ||
``` | ||
... and even return it to how it started | ||
```typescript | ||
SpyOn(some, "function"); | ||
some.function.restore(); | ||
// OR | ||
const spy = SpyOn(some, "function"); | ||
spy.restore(); | ||
``` | ||
#### Creating a spy from thin air | ||
You may want to just create a fake spy property this is easy to do and has all the same functionality as a Spy created through ```SpyOn``` | ||
```typescript | ||
import { FunctionSpy } from "alsatian"; | ||
const spy = new FunctionSpy(); | ||
``` | ||
#### Spying on a property | ||
Similarly to spying on functions you can also spy on properties as below ... | ||
```typescript | ||
import { SpyOnProperty } from "alsatian"; | ||
class Test { | ||
private _property: number = 42; | ||
get property() { | ||
return this._property; | ||
} | ||
set property(value: number) { | ||
this._property = value; | ||
} | ||
} | ||
const test = new Test(); | ||
SpyOnProperty(test, "property"); | ||
``` | ||
... then check it's been set ... | ||
```typescript | ||
const propertySpy = SpyOnProperty(test, "property"); | ||
// unlike function spies expect calls on property spies | ||
// only works using the returned spy from SpyOnProperty | ||
// and not the property itself | ||
Expect(propertySpy).toHaveBeenSet(); | ||
``` | ||
... or check it's been set to a specific value ... | ||
```typescript | ||
Expect(propertySpy).toHaveBeenSetTo(42); | ||
``` | ||
... add a fake getter ... | ||
```typescript | ||
SpyOnProperty(test, "property").andCallGetter(() => { return "something"; }); | ||
``` | ||
... or setter ... | ||
```typescript | ||
SpyOnProperty(test, "property").andCallSetter((value: any) => { doSomethingWith(value); }); | ||
``` | ||
... return a set value ... | ||
```typescript | ||
SpyOnProperty(test, "property").andReturnValue(42); | ||
``` | ||
... and restore it to how it was before | ||
```typescript | ||
const properySpy = SpyOnProperty(test, "property"); | ||
properySpy.restore(); | ||
``` | ||
### Async Tests | ||
You can also have asynchronous tests using the ```AsyncTest``` annotation, | ||
```typescript | ||
import { Expect, AsyncTest, TestFixture } from "alsatian"; | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@AsyncTest() | ||
public async asyncTest() { | ||
const result = await somethingToHappen(); | ||
Expect(result).toBe(1); | ||
} | ||
} | ||
``` | ||
If you can't use the async/await syntax then fear not you can use a promise! | ||
```typescript | ||
import { Expect, AsyncTest, TestFixture } from "alsatian"; | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@AsyncTest() | ||
public asyncTest() { | ||
return new Promise((resolve, reject) => { | ||
waitForSomethingToHappen((result: number) => { | ||
Expect(result).toBe(1); | ||
resolve(); | ||
}); | ||
}); | ||
} | ||
} | ||
``` | ||
Alsatian will fail an ```AsyncTest``` if it takes longer than 500 ms to execute. You can change this if you need to though using the ```Timeout``` decorators | ||
```typescript | ||
import { Expect, AsyncTest, Timeout, TestFixture } from "alsatian"; | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@AsyncTest() | ||
@Timeout(5000) // Alsatian will now wait 5 seconds before failing | ||
public async asyncTest() { | ||
const result = await somethingThatTakesAlmostFiveSeconds(); | ||
Expect(result).toBe(1); | ||
} | ||
} | ||
``` | ||
### Ignoring Tests | ||
You can stop tests from being run by using the ```IgnoreTest``` annotation | ||
```typescript | ||
import { Expect, Test, IgnoreTest, TestFixture } from "alsatian"; | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@Test() | ||
@IgnoreTest() | ||
public ignoredTest() { | ||
Expect(1).toBe(1); | ||
} | ||
} | ||
``` | ||
or you can stop all tests in a given fixture from running using the ```IgnoreTests``` annotation | ||
```typescript | ||
import { Expect, Test, IgnoreTests, TestFixture } from "alsatian"; | ||
@IgnoreTests() | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@Test() | ||
public thisTestWillNotBeRun() { | ||
Expect(1).toBe(1); | ||
} | ||
@Test() | ||
public neitherWillThisOne() { | ||
Expect(1).toBe(1); | ||
} | ||
} | ||
``` | ||
You can provide a reason to both of these, which will put it into the TAP output. | ||
```typescript | ||
import { Expect, Test, IgnoreTest, TestFixture } from "alsatian"; | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@Test() | ||
@IgnoreTest("This test is useless, ignore for now.") | ||
public ignoredTest() { | ||
Expect(1).toBe(1); | ||
} | ||
} | ||
``` | ||
### Focusing Tests | ||
You can run a single test or select tests using the ```FocusTest``` annotation | ||
```typescript | ||
import { Expect, Test, FocusTest, TestFixture } from "alsatian"; | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@Test() | ||
@FocusTest | ||
public thisTestWillBeRun() { | ||
Expect(1).toBe(1); | ||
} | ||
@Test() | ||
public thisTestWillNotBeRun() { | ||
Expect(1).toBe(1); | ||
} | ||
} | ||
``` | ||
or you can run only tests in this fixture using the ```FocusTests``` annotation | ||
```typescript | ||
import { Expect, Test, FocusTests, TestFixture } from "alsatian"; | ||
@FocusTests | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@Test() | ||
public thisTestWillBeRun() { | ||
Expect(1).toBe(1); | ||
} | ||
@Test() | ||
public soWillThisTest() { | ||
Expect(1).toBe(1); | ||
} | ||
} | ||
``` | ||
### Setup | ||
You can get a function to be run before every function in the fixture using the ```Setup``` decorators | ||
```typescript | ||
import { Expect, Test, Setup, TestFixture } from "alsatian"; | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@Setup | ||
public thisFunctionWillBeRunBeforeAllTests() { | ||
// do some setup work | ||
} | ||
@Test() | ||
public exampleTest() { | ||
Expect(1).toBe(1); | ||
} | ||
} | ||
``` | ||
### Teardown | ||
You can also run functions after every test has completed using the ```Teardown``` decorators | ||
```typescript | ||
import { Expect, Test, Teardown, TestFixture } from "alsatian"; | ||
@TestFixture("Example Test Fixture") | ||
export class ExampleTestFixture { | ||
@Teardown | ||
public thisFunctionWillBeRunAfterAllTests() { | ||
// do some teardown work | ||
} | ||
@Test() | ||
public exampleTest() { | ||
Expect(1).toBe(1); | ||
} | ||
} | ||
``` | ||
### Extending Expect | ||
Extending the Expect call in Alsatian is super simple as it's OO and extensible by default! All you need to do is extend... | ||
```typescript | ||
class MatcherExtension extends Matcher { | ||
isSomething() { | ||
if (this.actualValue !== "something") { | ||
throw new MatchError("should have been something", "something", "not something"); | ||
} | ||
// pass arguments into your test functions to keep your test code from being repetative | ||
@TestCase(2, 2, 4) | ||
@TestCase(2, 3, 5) | ||
@TestCase(3, 3, 6) | ||
@Test("addition tests") | ||
public addTest(firstNumber: number, secondNumber: number, expectedSum: number) { | ||
Expect(firstNumber + secondNumber).toBe(expectedSum); | ||
} | ||
} | ||
``` | ||
Then if you want to you can wrap it in a function to add some neat fluent syntax | ||
```typescript | ||
// name it whatever your heart desires | ||
export ExtendedExpect = (value: any) => new MatcherExtension(value); | ||
``` | ||
Here's an explanation of some of the concepts that are useful here | ||
## Why would I use Alsatian? | ||
#### this.actualValue | ||
The key question! Well Alsatian has a lot going for it here are just a few great things to note: | ||
This is the value that is added into the Matcher constructor / Expect function i.e. the value under test | ||
* All the awesome features you love from existing frameworks | ||
* The [TestCase](https://github.com/alsatian-test/alsatian/wiki/test-structure#test-cases) decorator allows you to write smaller, DRY and more readable tests | ||
* No globals! | ||
* TAP support so you can use your favourite TAP reporter | ||
* Great CI process, every pull request and push on every branch is scrutinised to ensure high quality | ||
* 100% coverage all statements, lines, branches are covered in Alsatian tests | ||
* Various services rate us very highly on lots of different factors, check out our badges | ||
* Everything is documented in a friendly and simple way to help you get to the unit test setup of your dreams | ||
* Being written in TypeScript it fits perfectly into your TypeScript but still compatible with JavaScript too! | ||
* Active support - if you've got a question, a suggestion or found an issue let us know and we'll get back to you quickly | ||
#### this.shouldMatch | ||
Also it's lightning fast, watch it run all of it's unit tests in super quick time! | ||
![Alsatian Test Run Video](https://github.com/alsatian-test/alsatian/raw/master/documentation/images/alsatian-test-run.gif) | ||
This indicates whether the not opperator has been used | ||
## Besides unit tests, what can I do with Alsatian | ||
```typescript | ||
Expect(something).toBe(nothing); // this.shouldMatch === true | ||
Expect(something).not.toBe(nothing); // this.shouldMatch === false | ||
``` | ||
So many awesome things! | ||
* write end to end tests with [Selenium](https://github.com/alsatian-test/alsatian/wiki/using-alsatian-with-selenium) | ||
* check your code coverage with [NYC](https://github.com/alsatian-test/alsatian/wiki/check-test-coverage-with-nyc) | ||
* set up a wonderful CI process and give confidence in the quality of your product | ||
* have the output look however you desire using [TAP reporters](https://github.com/alsatian-test/alsatian/wiki/using-alsatian-with-tap-reporters) | ||
#### MatchError | ||
## Support | ||
Throwing this error will tell Alsatian that the test found something wrong (you can extend this too). It has three arguments, the actual value, the expected value and a message. A usage example can be found below. | ||
If at any time things are unclear or you think there may be something going wrong feel free to [raise an issue](https://github.com/alsatian-test/alsatian/issues/new) and we'll be glad to get back to you with a solution quickly. | ||
```typescript | ||
throw new MatchError( | ||
"expected nothing to be something, but it wasn't.", // an explanation of the issue | ||
"something", // what the value was expected to be | ||
"nothing" // what the value actually was | ||
); | ||
``` | ||
## Contributing | ||
You may also set each value independently if you extend them (as the setters are protected) | ||
We're always glad to have help out with Alsatian, check out the [guidelines](https://github.com/alsatian-test/alsatian/blob/master/CONTRIBUTING.md) | ||
```typescript | ||
export class ExtendedMatchError { | ||
public constructor() { | ||
super(); | ||
this.message = "expected nothing to be something, but it wasn't."; | ||
this._expected = "something"; | ||
this._actual = "nothing"; | ||
} | ||
} | ||
``` | ||
## License | ||
#### Example assertion function | ||
```typescript | ||
public toBeHexCode() { | ||
// check whether the value provided in Expect() is a hex code or not | ||
const isHexCode = /^#[A-F|0-9]{6}$/i.test(this.actualValue); | ||
// if the value should have been a hex code and wasn't | ||
// or should not have been and was | ||
if (isHexCode !== this.shouldMatch) { | ||
// output for Alsatian that it should have been a hex code | ||
if (this.shouldMatch) { | ||
throw new MatchError( | ||
`expected {this.actualValue} to be a hex code but it wasn't.`, | ||
"a hex code", | ||
"not a hex code" | ||
); | ||
} | ||
// output for Alsatian that it should not have been a hex code | ||
else { | ||
throw new MatchError( | ||
`expected {this.actualValue} to not be a hex code but it wasn't.`, | ||
"not a hex code", | ||
"a hex code" | ||
); | ||
} | ||
} | ||
} | ||
``` | ||
#### usage | ||
Now you're ready to use your extended ```Expect```. This is super easy... | ||
```typescript | ||
import { ExtendedExpect as Expect } from "./your/extended-expect/location"; | ||
import { TestFixture, Test } from "alsatian"; | ||
@TestFixture("color tests") | ||
export default class ColorTestFixture { | ||
@Test("check hexcodes") | ||
public checkHexcodes { | ||
Expect("#00000").toBeHexCode(); | ||
} | ||
} | ||
``` | ||
Alsatian has been released under the [MIT license](https://github.com/alsatian-test/alsatian/blob/master/LICENSE) |
Sorry, the diff of this file is not supported yet
891449
185
3716
111