UNPKG

5.56 kBPlain TextView Raw
1/**
2 * @module botkit
3 */
4/**
5 * Copyright (c) Microsoft Corporation. All rights reserved.
6 * Licensed under the MIT License.
7 */
8import {
9 Activity,
10 AutoSaveStateMiddleware,
11 ConversationState,
12 MemoryStorage,
13 Middleware,
14 TestAdapter,
15 TurnContext
16} from 'botbuilder-core';
17import { Dialog, DialogSet, DialogTurnResult, DialogTurnStatus } from 'botbuilder-dialogs';
18import { Botkit } from './core';
19
20/**
21 * A client for testing dialogs in isolation.
22 */
23export class BotkitTestClient {
24 private readonly _callback: (turnContext: TurnContext) => Promise<void>;
25 private readonly _testAdapter: TestAdapter;
26 public dialogTurnResult: DialogTurnResult;
27 public conversationState: ConversationState;
28
29 /**
30 * Create a BotkitTestClient to test a dialog without having to create a full-fledged adapter.
31 *
32 * ```javascript
33 * let client = new BotkitTestClient('test', bot, MY_DIALOG, MY_OPTIONS);
34 * let reply = await client.sendActivity('first message');
35 * assert.strictEqual(reply.text, 'first reply', 'reply failed');
36 * ```
37 *
38 * @param channelId The channelId to be used for the test.
39 * Use 'emulator' or 'test' if you are uncertain of the channel you are targeting.
40 * Otherwise, it is recommended that you use the id for the channel(s) your bot will be using and write a test case for each channel.
41 * @param bot (Required) The Botkit bot that has the skill to test.
42 * @param dialogToTest (Required) The identifier of the skill to test in the bot.
43 * @param initialDialogOptions (Optional) additional argument(s) to pass to the dialog being started.
44 * @param middlewares (Optional) a stack of middleware to be run when testing
45 * @param conversationState (Optional) A ConversationState instance to use in the test client
46 */
47 public constructor(channelId: string, bot: Botkit, dialogToTest: string | string[], initialDialogOptions?: any, middlewares?: Middleware[], conversationState?: ConversationState)
48 public constructor(testAdapter: TestAdapter, bot: Botkit, dialogToTest: string | string[], initialDialogOptions?: any, middlewares?: Middleware[], conversationState?: ConversationState)
49 public constructor(channelOrAdapter: string | TestAdapter, bot: Botkit, dialogToTest: string | string[], initialDialogOptions?: any, middlewares?: Middleware[], conversationState?: ConversationState) {
50 this.conversationState = conversationState || new ConversationState(new MemoryStorage());
51
52 const dialogState = this.conversationState.createProperty('DialogState');
53
54 let targetDialogs = [];
55 if (Array.isArray(dialogToTest)) {
56 dialogToTest.forEach((dialogName) => {
57 targetDialogs.push(
58 bot.dialogSet.find(dialogName)
59 );
60 targetDialogs.push(
61 bot.dialogSet.find(dialogName + '_default_prompt')
62 );
63 targetDialogs.push(
64 bot.dialogSet.find(dialogName + ':botkit-wrapper')
65 );
66 });
67 dialogToTest = dialogToTest[0];
68 } else {
69 targetDialogs = [
70 bot.dialogSet.find(dialogToTest),
71 bot.dialogSet.find(dialogToTest + '_default_prompt'),
72 bot.dialogSet.find(dialogToTest + ':botkit-wrapper')
73 ];
74 }
75
76 this._callback = this.getDefaultCallback(targetDialogs, initialDialogOptions || null, dialogState);
77
78 if (typeof channelOrAdapter === 'string') {
79 this._testAdapter = new TestAdapter(this._callback, { channelId: channelOrAdapter }).use(new AutoSaveStateMiddleware(this.conversationState));
80 } else {
81 this._testAdapter = channelOrAdapter;
82 }
83
84 this.addUserMiddlewares(middlewares);
85 }
86
87 /**
88 * Send an activity into the dialog.
89 * @returns a TestFlow that can be used to assert replies etc
90 * @param activity an activity potentially with text
91 *
92 * ```javascript
93 * DialogTest.send('hello').assertReply('hello yourself').then(done);
94 * ```
95 */
96 public async sendActivity(activity: Partial<Activity> | string): Promise<any> {
97 if (!activity) { activity = { type: 'event' }}
98 await this._testAdapter.receiveActivity(activity);
99 return this._testAdapter.activityBuffer.shift();
100 }
101
102 /**
103 * Get the next reply waiting to be delivered (if one exists)
104 */
105 public getNextReply(): Partial<Activity> {
106 return this._testAdapter.activityBuffer.shift();
107 }
108
109 private getDefaultCallback(targetDialogs: Dialog[], initialDialogOptions: any, dialogState: any): (turnContext: TurnContext) => Promise<void> {
110 return async (turnContext: TurnContext): Promise<void> => {
111 const dialogSet = new DialogSet(dialogState);
112 targetDialogs.forEach(targetDialog => dialogSet.add(targetDialog));
113 const dialogContext = await dialogSet.createContext(turnContext);
114 this.dialogTurnResult = await dialogContext.continueDialog();
115 if (this.dialogTurnResult.status === DialogTurnStatus.empty) {
116 this.dialogTurnResult = await dialogContext.beginDialog(targetDialogs[0].id, initialDialogOptions);
117 }
118 };
119 }
120
121 private addUserMiddlewares(middlewares: Middleware[]): void {
122 if (middlewares != null) {
123 middlewares.forEach((middleware) => {
124 this._testAdapter.use(middleware);
125 });
126 }
127 }
128}