spec/Serenity.spec.ts
import 'mocha';
import sinon = require('sinon');
import { ConfigurationError } from '../src';
import { ActivityFinished, ActivityStarts, DomainEvent, TestRunnerDetected } from '../src/events';
import { OutputStream } from '../src/io';
import { CorrelationId, Name } from '../src/model';
import { Actor, Interaction } from '../src/screenplay';
import { Serenity } from '../src/Serenity';
import { Cast, Clock, ListensToDomainEvents, Stage, StageCrewMember, StageCrewMemberBuilder } from '../src/stage';
import { StageCrewMemberBuilderDependencies } from '../src/stage/StageCrewMemberBuilderDependencies';
import { expect } from './expect';
describe('Serenity', () => {
describe('when constructing a Stage', () => {
it('connects it with a provided Cast', () => {
const prepareSpy = sinon.spy();
class Extras implements Cast {
prepare(actor: Actor): Actor {
prepareSpy(actor);
return actor;
}
}
const serenity = new Serenity(new Clock());
serenity.configure({
actors: new Extras(),
});
const Joe = serenity.theActorCalled('Joe');
expect(prepareSpy).to.have.been.calledOnce;
expect(prepareSpy.getCall(0).args[0]).to.equal(Joe);
});
it('connects it with provided StageCrewMembers', () => {
const stageCrewMember: StageCrewMember = {
assignedTo: sinon.spy(),
notifyOf(event: DomainEvent) { }
}
const serenity = new Serenity(new Clock());
serenity.configure({
crew: [
stageCrewMember
]
});
expect(stageCrewMember.assignedTo).to.have.been.calledOnceWith(sinon.match.instanceOf(Stage));
});
it('injects dependencies into StageCrewMemberBuilders', () => {
const stageCrewMemberBuilder: StageCrewMemberBuilder = {
build: sinon.spy(),
};
const outputStream: OutputStream = {
write(content: string) {
}
}
const serenity = new Serenity(new Clock());
serenity.configure({
outputStream,
crew: [
stageCrewMemberBuilder
]
});
expect(stageCrewMemberBuilder.build).to.have.been.calledOnceWith(
sinon.match.has('stage', sinon.match.instanceOf(Stage))
.and(
sinon.match.has('outputStream', outputStream)
)
);
});
it(`complains when a provided crew member doesn't implement StageCrewMember or StageCrewMemberBuilder interfaces`, () => {
const stageCrewMemberBuilder: StageCrewMemberBuilder = {
build(dependencies: StageCrewMemberBuilderDependencies): ListensToDomainEvents {
return {
notifyOf(event: DomainEvent): void {
}
};
}
};
const serenity = new Serenity(new Clock());
expect(() => {
serenity.configure({
crew: [
stageCrewMemberBuilder,
undefined,
]
});
}).to.throw(ConfigurationError,
'Entries under `crew` should implement either StageCrewMember or StageCrewMemberBuilder interfaces, `undefined` found at index 1'
)
});
});
it(`enables propagation of DomainEvents triggered by Actors' Activities and StageCrewMembers`, () => {
class Extras implements Cast {
prepare(actor: Actor): Actor {
return actor;
}
}
const PerformSomeInteraction = () =>
Interaction.where(`#actor performs some interaction`, actor => {
return void 0;
});
const frozenClock = new Clock(() => new Date('1983-07-03'));
const serenity = new Serenity(frozenClock);
const listener = new Listener<ActivityStarts | ActivityFinished>();
serenity.configure({
actors: new Extras(),
crew: [ listener ],
});
return serenity.theActorCalled('Joe').attemptsTo(
PerformSomeInteraction(),
).
then(() => serenity.waitForNextCue()).
then(() => {
expect(listener.events).to.have.lengthOf(2);
expect(listener.events[0]).to.be.instanceOf(ActivityStarts);
expect(listener.events[0].details.name.value).to.equal(`Joe performs some interaction`);
expect(listener.events[1]).to.be.instanceOf(ActivityFinished);
expect(listener.events[1].details.name.value).to.equal(`Joe performs some interaction`);
});
});
it('allows for external parties, such as test runner adapters, to announce DomainEvents', () => {
const frozenClock = new Clock(() => new Date('1983-07-03'));
const serenity = new Serenity(frozenClock);
const listener = new Listener<TestRunnerDetected>();
const testRunnerName = new Name('mocha');
serenity.configure({ crew: [ listener ] });
serenity.announce(new TestRunnerDetected(CorrelationId.create(), testRunnerName, serenity.currentTime()));
return serenity.waitForNextCue().
then(() => {
expect(listener.events).to.have.lengthOf(1);
expect(listener.events[0]).to.be.instanceOf(TestRunnerDetected);
expect(listener.events[0].name).to.equal(testRunnerName);
});
});
class Listener<Event_Type extends DomainEvent> implements StageCrewMember {
public readonly events: Event_Type[] = [];
constructor(private stage?: Stage) {
}
assignedTo(stage: Stage): StageCrewMember {
this.stage = stage;
return this;
}
notifyOf(event: Event_Type): void {
this.events.push(event);
}
}
});