Adds new configuration option to failSpecWithNoExpectations that will report specs without expectations as failures if enabled

This commit is contained in:
Dmitriy T
2019-08-22 16:08:43 -04:00
committed by Steve Gravrock
parent e8870db8d3
commit 7263a38c3f
9 changed files with 172 additions and 48 deletions

View File

@@ -118,7 +118,7 @@ The easiest way to run the tests in **Internet Explorer** is to run a VM that ha
1. Ensure all specs are green in browser *and* node 1. Ensure all specs are green in browser *and* node
1. Ensure eslint and prettier are clean as part of your `npm test` command. You can run `npm run cleanup` to have prettier re-write the files. 1. Ensure eslint and prettier are clean as part of your `npm test` command. You can run `npm run cleanup` to have prettier re-write the files.
1. Build `jasmine.js` with `grunt buildDistribution` and run all specs again - this ensures that your changes self-test well 1. Build `jasmine.js` with `npm run build` and run all specs again - this ensures that your changes self-test well
1. Revert your changes to `jasmine.js` and `jasmine-html.js` 1. Revert your changes to `jasmine.js` and `jasmine-html.js`
* We do this because `jasmine.js` and `jasmine-html.js` are auto-generated (as you've seen in the previous steps) and accepting multiple pull requests when this auto-generated file changes causes lots of headaches * We do this because `jasmine.js` and `jasmine-html.js` are auto-generated (as you've seen in the previous steps) and accepting multiple pull requests when this auto-generated file changes causes lots of headaches
* When we accept your pull request, we will generate these files as a separate commit and merge the entire branch into master * When we accept your pull request, we will generate these files as a separate commit and merge the entire branch into master

View File

@@ -16,6 +16,7 @@
"posttest": "eslint src/**/*.js spec/**/*.js && prettier --check src/**/*.js spec/**/*.js", "posttest": "eslint src/**/*.js spec/**/*.js && prettier --check src/**/*.js spec/**/*.js",
"test": "grunt execSpecsInNode", "test": "grunt execSpecsInNode",
"cleanup": "prettier --write src/**/*.js spec/**/*.js", "cleanup": "prettier --write src/**/*.js spec/**/*.js",
"build": "grunt buildDistribution",
"serve": "node spec/support/localJasmineBrowser.js", "serve": "node spec/support/localJasmineBrowser.js",
"serve:performance": "node spec/support/localJasmineBrowser.js jasmine-browser-performance.json", "serve:performance": "node spec/support/localJasmineBrowser.js jasmine-browser-performance.json",
"ci": "node spec/support/ci.js", "ci": "node spec/support/ci.js",

View File

@@ -296,7 +296,7 @@ describe('TreeProcessor', function() {
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn('foo'); queueRunner.calls.mostRecent().args[0].queueableFns[0].fn('foo');
expect(leaf.execute).toHaveBeenCalledWith('foo', false); expect(leaf.execute).toHaveBeenCalledWith('foo', false, false);
}); });
it('runs a node with no children', function() { it('runs a node with no children', function() {
@@ -368,10 +368,10 @@ describe('TreeProcessor', function() {
expect(queueableFns.length).toBe(3); expect(queueableFns.length).toBe(3);
queueableFns[1].fn('foo'); queueableFns[1].fn('foo');
expect(leaf1.execute).toHaveBeenCalledWith('foo', false); expect(leaf1.execute).toHaveBeenCalledWith('foo', false, false);
queueableFns[2].fn('bar'); queueableFns[2].fn('bar');
expect(leaf2.execute).toHaveBeenCalledWith('bar', false); expect(leaf2.execute).toHaveBeenCalledWith('bar', false, false);
}); });
it('cascades errors up the tree', function() { it('cascades errors up the tree', function() {
@@ -397,7 +397,7 @@ describe('TreeProcessor', function() {
expect(queueableFns.length).toBe(2); expect(queueableFns.length).toBe(2);
queueableFns[1].fn('foo'); queueableFns[1].fn('foo');
expect(leaf.execute).toHaveBeenCalledWith('foo', false); expect(leaf.execute).toHaveBeenCalledWith('foo', false, false);
queueRunner.calls.mostRecent().args[0].onComplete('things'); queueRunner.calls.mostRecent().args[0].onComplete('things');
expect(nodeComplete).toHaveBeenCalled(); expect(nodeComplete).toHaveBeenCalled();
@@ -433,7 +433,7 @@ describe('TreeProcessor', function() {
expect(nodeStart).toHaveBeenCalledWith(node, 'bar'); expect(nodeStart).toHaveBeenCalledWith(node, 'bar');
queueableFns[1].fn('foo'); queueableFns[1].fn('foo');
expect(leaf1.execute).toHaveBeenCalledWith('foo', true); expect(leaf1.execute).toHaveBeenCalledWith('foo', true, false);
node.getResult.and.returnValue({ im: 'disabled' }); node.getResult.and.returnValue({ im: 'disabled' });
@@ -445,6 +445,35 @@ describe('TreeProcessor', function() {
); );
}); });
it('should execute node with correct arguments when failSpecWithNoExpectations option is set', function() {
var leaf = new Leaf(),
node = new Node({ children: [leaf] }),
root = new Node({ children: [node] }),
queueRunner = jasmine.createSpy('queueRunner'),
nodeStart = jasmine.createSpy('nodeStart'),
nodeComplete = jasmine.createSpy('nodeComplete'),
processor = new jasmineUnderTest.TreeProcessor({
tree: root,
runnableIds: [],
queueRunnerFactory: queueRunner,
nodeStart: nodeStart,
nodeComplete: nodeComplete,
failSpecWithNoExpectations: true
}),
treeComplete = jasmine.createSpy('treeComplete'),
nodeDone = jasmine.createSpy('nodeDone');
processor.execute(treeComplete);
var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns;
queueableFns[0].fn(nodeDone);
queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns;
expect(queueableFns.length).toBe(2);
queueableFns[1].fn('foo');
expect(leaf.execute).toHaveBeenCalledWith('foo', true, true);
});
it('runs beforeAlls for a node with children', function() { it('runs beforeAlls for a node with children', function() {
var leaf = new Leaf(), var leaf = new Leaf(),
node = new Node({ node = new Node({
@@ -600,11 +629,11 @@ describe('TreeProcessor', function() {
queueableFns[0].fn(); queueableFns[0].fn();
expect(nonSpecified.execute).not.toHaveBeenCalled(); expect(nonSpecified.execute).not.toHaveBeenCalled();
expect(specified.execute).toHaveBeenCalledWith(undefined, false); expect(specified.execute).toHaveBeenCalledWith(undefined, false, false);
queueableFns[1].fn(); queueableFns[1].fn();
expect(nonSpecified.execute).toHaveBeenCalledWith(undefined, true); expect(nonSpecified.execute).toHaveBeenCalledWith(undefined, true, false);
}); });
it('runs nodes and leaves with a specified order', function() { it('runs nodes and leaves with a specified order', function() {

View File

@@ -2138,6 +2138,41 @@ describe("Env integration", function() {
}); });
}); });
describe('when spec has no expectations', function() {
var env, reporter;
beforeEach(function() {
env = new jasmineUnderTest.Env();
reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']);
env.addReporter(reporter);
env.it('is a spec without any expectations', function() {
// does nothing, just a mock spec without expectations
});
});
it('should report "failed" status if "failSpecWithNoExpectations" is enabled', function(done) {
reporter.jasmineDone.and.callFake(function(e) {
expect(e.overallStatus).toEqual('failed');
done();
});
env.configure({ failSpecWithNoExpectations: true });
env.execute();
});
it('should report "passed" status if "failSpecWithNoExpectations" is disabled', function(done) {
reporter.jasmineDone.and.callFake(function(e) {
expect(e.overallStatus).toEqual('passed');
done();
});
env.configure({ failSpecWithNoExpectations: false });
env.execute();
});
});
describe('When a top-level beforeAll fails', function() { describe('When a top-level beforeAll fails', function() {
it('is "failed"', function(done) { it('is "failed"', function(done) {
var env = new jasmineUnderTest.Env(), var env = new jasmineUnderTest.Env(),

View File

@@ -62,19 +62,19 @@ describe('HtmlReporter', function() {
}); });
describe('when a spec is done', function() { describe('when a spec is done', function() {
it('logs errors to the console and prints a special symbol if it is an empty spec', function() { describe('and no expectations ran', function() {
if (typeof console === 'undefined') { var container, reporter;
console = { warn: function() {} }; beforeEach(function() {
} if (typeof console === 'undefined') {
console = { warn: function() {}, error: function() {} };
}
var env = new jasmineUnderTest.Env(), container = document.createElement('div');
container = document.createElement('div'),
getContainer = function() {
return container;
},
reporter = new jasmineUnderTest.HtmlReporter({ reporter = new jasmineUnderTest.HtmlReporter({
env: env, env: new jasmineUnderTest.Env(),
getContainer: getContainer, getContainer: function() {
return container;
},
createElement: function() { createElement: function() {
return document.createElement.apply(document, arguments); return document.createElement.apply(document, arguments);
}, },
@@ -83,21 +83,39 @@ describe('HtmlReporter', function() {
} }
}); });
spyOn(console, 'warn'); spyOn(console, 'warn');
spyOn(console, 'error');
reporter.initialize(); reporter.initialize();
});
reporter.specDone({
status: 'passed', it('should log warning to the console and print a special symbol when empty spec status is passed', function() {
fullName: 'Some Name', reporter.specDone({
passedExpectations: [], status: 'passed',
failedExpectations: [] fullName: 'Some Name',
passedExpectations: [],
failedExpectations: []
});
expect(console.warn).toHaveBeenCalledWith(
"Spec 'Some Name' has no expectations."
);
var specEl = container.querySelector('.jasmine-symbol-summary li');
expect(specEl.getAttribute('class')).toEqual('jasmine-empty');
});
it('should log error to the console and print a failure symbol when empty spec status is failed', function() {
reporter.specDone({
status: 'failed',
fullName: 'Some Name',
passedExpectations: [],
failedExpectations: []
});
expect(console.error).toHaveBeenCalledWith(
"Spec 'Some Name' has no expectations."
);
var specEl = container.querySelector('.jasmine-symbol-summary li');
expect(specEl.getAttribute('class')).toEqual('jasmine-failed');
}); });
expect(console.warn).toHaveBeenCalledWith(
"Spec 'Some Name' has no expectations."
);
var specEl = container.querySelector('.jasmine-symbol-summary li');
expect(specEl.getAttribute('class')).toEqual('jasmine-empty');
}); });
it('reports the status symbol of a excluded spec', function() { it('reports the status symbol of a excluded spec', function() {

View File

@@ -65,6 +65,16 @@ getJasmineRequireObj().Env = function(j$) {
* @default false * @default false
*/ */
failFast: false, failFast: false,
/**
* Whether to fail the spec if it ran no expectations. By default
* a spec that ran no expectations is reported as passed. Setting this
* to true will report such spec as a failure.
* @name Configuration#failSpecWithNoExpectations
* @since 3.5.0
* @type Boolean
* @default false
*/
failSpecWithNoExpectations: false,
/** /**
* Whether to cause specs to only have one expectation failure. * Whether to cause specs to only have one expectation failure.
* @name Configuration#oneFailurePerSpec * @name Configuration#oneFailurePerSpec
@@ -167,6 +177,11 @@ getJasmineRequireObj().Env = function(j$) {
config.failFast = configuration.failFast; config.failFast = configuration.failFast;
} }
if (configuration.hasOwnProperty('failSpecWithNoExpectations')) {
config.failSpecWithNoExpectations =
configuration.failSpecWithNoExpectations;
}
if (configuration.hasOwnProperty('oneFailurePerSpec')) { if (configuration.hasOwnProperty('oneFailurePerSpec')) {
config.oneFailurePerSpec = configuration.oneFailurePerSpec; config.oneFailurePerSpec = configuration.oneFailurePerSpec;
} }
@@ -641,6 +656,7 @@ getJasmineRequireObj().Env = function(j$) {
tree: topSuite, tree: topSuite,
runnableIds: runnablesToRun, runnableIds: runnablesToRun,
queueRunnerFactory: queueRunnerFactory, queueRunnerFactory: queueRunnerFactory,
failSpecWithNoExpectations: config.failSpecWithNoExpectations,
nodeStart: function(suite, next) { nodeStart: function(suite, next) {
currentlyExecutingSuites.push(suite); currentlyExecutingSuites.push(suite);
defaultResourcesForRunnable(suite.id, suite.parentSuite.id); defaultResourcesForRunnable(suite.id, suite.parentSuite.id);

View File

@@ -82,7 +82,7 @@ getJasmineRequireObj().Spec = function(j$) {
return this.asyncExpectationFactory(actual, this); return this.asyncExpectationFactory(actual, this);
}; };
Spec.prototype.execute = function(onComplete, excluded) { Spec.prototype.execute = function(onComplete, excluded, failSpecWithNoExp) {
var self = this; var self = this;
var onStart = { var onStart = {
@@ -95,7 +95,7 @@ getJasmineRequireObj().Spec = function(j$) {
var complete = { var complete = {
fn: function(done) { fn: function(done) {
self.queueableFn.fn = null; self.queueableFn.fn = null;
self.result.status = self.status(excluded); self.result.status = self.status(excluded, failSpecWithNoExp);
self.resultCallback(self.result, done); self.resultCallback(self.result, done);
} }
}; };
@@ -166,7 +166,7 @@ getJasmineRequireObj().Spec = function(j$) {
return this.result; return this.result;
}; };
Spec.prototype.status = function(excluded) { Spec.prototype.status = function(excluded, failSpecWithNoExpectations) {
if (excluded === true) { if (excluded === true) {
return 'excluded'; return 'excluded';
} }
@@ -175,11 +175,17 @@ getJasmineRequireObj().Spec = function(j$) {
return 'pending'; return 'pending';
} }
if (this.result.failedExpectations.length > 0) { if (
this.result.failedExpectations.length > 0 ||
(failSpecWithNoExpectations &&
this.result.failedExpectations.length +
this.result.passedExpectations.length ===
0)
) {
return 'failed'; return 'failed';
} else {
return 'passed';
} }
return 'passed';
}; };
Spec.prototype.getFullName = function() { Spec.prototype.getFullName = function() {

View File

@@ -5,6 +5,7 @@ getJasmineRequireObj().TreeProcessor = function() {
queueRunnerFactory = attrs.queueRunnerFactory, queueRunnerFactory = attrs.queueRunnerFactory,
nodeStart = attrs.nodeStart || function() {}, nodeStart = attrs.nodeStart || function() {},
nodeComplete = attrs.nodeComplete || function() {}, nodeComplete = attrs.nodeComplete || function() {},
failSpecWithNoExpectations = !!attrs.failSpecWithNoExpectations,
orderChildren = orderChildren =
attrs.orderChildren || attrs.orderChildren ||
function(node) { function(node) {
@@ -222,7 +223,11 @@ getJasmineRequireObj().TreeProcessor = function() {
} else { } else {
return { return {
fn: function(done) { fn: function(done) {
node.execute(done, stats[node.id].excluded); node.execute(
done,
stats[node.id].excluded,
failSpecWithNoExpectations
);
} }
}; };
} }

View File

@@ -112,12 +112,13 @@ jasmineRequire.HtmlReporter = function(j$) {
this.specDone = function(result) { this.specDone = function(result) {
stateBuilder.specDone(result); stateBuilder.specDone(result);
if ( if (noExpectations(result)) {
noExpectations(result) && var noSpecMsg = "Spec '" + result.fullName + "' has no expectations.";
typeof console !== 'undefined' && if (result.status === 'failed') {
typeof console.error !== 'undefined' console.error(noSpecMsg);
) { } else {
console.warn("Spec '" + result.fullName + "' has no expectations."); console.warn(noSpecMsg);
}
} }
if (!symbols) { if (!symbols) {
@@ -140,7 +141,7 @@ jasmineRequire.HtmlReporter = function(j$) {
}; };
this.displaySpecInCorrectFormat = function(result) { this.displaySpecInCorrectFormat = function(result) {
return noExpectations(result) return noExpectations(result) && result.status === 'passed'
? 'jasmine-empty' ? 'jasmine-empty'
: this.resultStatus(result.status); : this.resultStatus(result.status);
}; };
@@ -363,6 +364,16 @@ jasmineRequire.HtmlReporter = function(j$) {
); );
} }
if (result.failedExpectations.length === 0) {
messages.appendChild(
createDom(
'div',
{ className: 'jasmine-result-message' },
'Spec has no expectations'
)
);
}
return failure; return failure;
} }
@@ -654,9 +665,12 @@ jasmineRequire.HtmlReporter = function(j$) {
} }
function noExpectations(result) { function noExpectations(result) {
var allExpectations =
result.failedExpectations.length + result.passedExpectations.length;
return ( return (
result.failedExpectations.length + result.passedExpectations.length === allExpectations === 0 &&
0 && result.status === 'passed' (result.status === 'passed' || result.status === 'failed')
); );
} }