Skip to content
Permalink
Browse files
feat(assertions): property(name, expectation) allows to assert on a p…
…roperty of an object
  • Loading branch information
jan-molak committed Mar 6, 2019
1 parent 3c8f8bf commit feaaf7914cb852fcff222027e86d8137d47e61ba
@@ -2,8 +2,8 @@ import 'mocha';
import { given } from 'mocha-testdata';

import { expect } from '@integration/testing-tools';
import { Actor, AssertionError, KnowableUnknown } from '@serenity-js/core';
import { Ensure, equals } from '../src';
import { Actor, AnswersQuestions, AssertionError, KnowableUnknown, LogicError } from '@serenity-js/core';
import { Ensure, equals, Expectation, Outcome } from '../src';
import { isIdenticalTo, p, q } from './fixtures';

/** @test {Ensure} */
@@ -51,4 +51,21 @@ describe('Ensure', () => {
Ensure.that(actual, isIdenticalTo(42)),
)).to.be.fulfilled;
});

/** @test {Ensure.that} */
it(`complains when given an Expectation that doesn't conform to the interface`, () => {
class BrokenExpectation<Expected, Actual> extends Expectation<Expected, Actual> {
answeredBy(actor: AnswersQuestions): (actual: Actual) => Promise<Outcome<any, Actual>> {
return (actual: Actual) => Promise.resolve(null);
}

toString(): string {
return `broken`;
}
}

return expect(Enrique.attemptsTo(
Ensure.that(4, new BrokenExpectation()),
)).to.be.rejectedWith(LogicError, 'An Expectation should return an instance of an Outcome, not null');
});
});
@@ -0,0 +1,30 @@
import 'mocha';

import { expect } from '@integration/testing-tools';
import { Actor, AssertionError } from '@serenity-js/core';
import { Ensure, equals, property } from '../../src';

describe('hasProperty', () => {

const Astrid = Actor.named('Astrid');

/** @test {hasProperty} */
it(`allows for the actor flow to continue when the "actual" has a property that meets the expectation`, () => {
return expect(Astrid.attemptsTo(
Ensure.that('Hello!', property('length', equals(6))),
)).to.be.fulfilled;
});

/** @test {hasProperty} */
it(`breaks the actor flow when "actual" does not have a property that meets the expectation`, () => {
return expect(Astrid.attemptsTo(
Ensure.that('Hello!', property('length', equals(0))),
)).to.be.rejectedWith(AssertionError, `Expected 'Hello!' to have property 'length' that does equal 0`);
});

/** @test {hasProperty} */
it(`contributes to a human-readable description`, () => {
expect(Ensure.that('Hello!', property('length', equals(6))).toString())
.to.equal(`#actor ensures that 'Hello!' does have property 'length' that does equal 6`);
});
});
@@ -1,9 +1,9 @@
import { AnswersQuestions, AssertionError, Interaction, KnowableUnknown } from '@serenity-js/core';
import { AnswersQuestions, AssertionError, Interaction, KnowableUnknown, Log, LogicError } from '@serenity-js/core';
import { formatted } from '@serenity-js/core/lib/io';
import { match } from 'tiny-types';

import { Expectation } from './Expectation';
import { ExpectationNotMet, Outcome } from './outcomes';
import { ExpectationMet, ExpectationNotMet, Outcome } from './outcomes';

export class Ensure<Actual> implements Interaction {
static that<A>(actual: KnowableUnknown<A>, expectation: Expectation<any, A>) {
@@ -31,7 +31,10 @@ export class Ensure<Actual> implements Interaction {
o.actual,
);
})
.else(_ => void 0),
.when(ExpectationMet, _ => void 0)
.else(o => {
throw new LogicError(formatted `An Expectation should return an instance of an Outcome, not ${ o }`);
}),
),
);
}
@@ -8,4 +8,5 @@ export * from './isLessThan';
export * from './matches';
export * from './not';
export * from './or';
export * from './property';
export * from './startsWith';
@@ -0,0 +1,37 @@
import { AnswersQuestions } from '@serenity-js/core';
import { formatted } from '@serenity-js/core/lib/io';

import { Expectation } from '../Expectation';
import { ExpectationMet, ExpectationNotMet, Outcome } from '../outcomes';

export function property<Actual, Property extends keyof Actual>(
propertyName: Property,
expectation: Expectation<any, Actual[Property]>,
): Expectation<Actual[Property], Actual> {
return new HasProperty(propertyName, expectation);
}

class HasProperty<Property extends keyof Actual, Actual> extends Expectation<Actual[Property], Actual> {
constructor(
private readonly propertyName: Property,
private readonly expectation: Expectation<any, Actual[Property]>,
) {
super();
}

answeredBy(actor: AnswersQuestions): (actual: Actual) => Promise<Outcome<any, Actual>> {

return (actual: Actual) =>
this.expectation.answeredBy(actor)(actual[this.propertyName])
.then((outcome: Outcome<any, Actual[Property]>) => {

return outcome instanceof ExpectationMet
? new ExpectationMet(this.toString(), outcome.expected, actual)
: new ExpectationNotMet(this.toString(), outcome.expected, actual);
});
}

toString(): string {
return formatted `have property ${ this.propertyName } that does ${ this.expectation }`;
}
}

0 comments on commit feaaf79

Please sign in to comment.