Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Botkit dialog test client #1815

Merged
merged 3 commits into from
Nov 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/botkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
],
"scripts": {
"build": "tsc",
"eslint": "./node_modules/.bin/eslint --fix src/*"
"eslint": "./node_modules/.bin/eslint --fix src/*",
"test": "tsc ; nyc mocha tests/*.tests.js"
},
"author": "benbrown@gmail.com",
"license": "MIT",
Expand Down
1 change: 1 addition & 0 deletions packages/botkit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './core';
export * from './conversation';
export * from './botworker';
export * from './dialogWrapper';
export * from './testClient';
116 changes: 116 additions & 0 deletions packages/botkit/src/testClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* @module botkit
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
Activity,
AutoSaveStateMiddleware,
ConversationState,
MemoryStorage,
Middleware,
TestAdapter,
TurnContext
} from 'botbuilder-core';
import { Dialog, DialogSet, DialogTurnResult, DialogTurnStatus } from 'botbuilder-dialogs';
import { Botkit } from "./core";

/**
* A client for testing dialogs in isolation.
*/
export class BotkitTestClient {

private readonly _callback: (turnContext: TurnContext) => Promise<void>;
private readonly _testAdapter: TestAdapter;
public dialogTurnResult: DialogTurnResult;
public conversationState: ConversationState;

/**
* Create a BotkitTestClient to test a dialog without having to create a full-fledged adapter.
*
* ```javascript
* let client = new BotkitTestClient('test', bot, MY_DIALOG, MY_OPTIONS);
* let reply = await client.sendActivity('first message');
* assert.strictEqual(reply.text, 'first reply', 'reply failed');
* ```
*
* @param channelId The channelId to be used for the test.
* Use 'emulator' or 'test' if you are uncertain of the channel you are targeting.
* 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.
* @param bot (Required) The Botkit bot that has the skill to test.
* @param dialogToTest (Required) The identifier of the skill to test in the bot.
* @param initialDialogOptions (Optional) additional argument(s) to pass to the dialog being started.
* @param middlewares (Optional) a stack of middleware to be run when testing
* @param conversationState (Optional) A ConversationState instance to use in the test client
*/
public constructor(channelId: string, bot: Botkit, dialogToTest: string, initialDialogOptions?: any, middlewares?: Middleware[], conversationState?: ConversationState);
public constructor(testAdapter: TestAdapter, bot: Botkit, dialogToTest: string, initialDialogOptions?: any, middlewares?: Middleware[], conversationState?: ConversationState)
constructor(channelOrAdapter: string | TestAdapter, bot: Botkit, dialogToTest: string, initialDialogOptions?: any, middlewares?: Middleware[], conversationState?: ConversationState) {
this.conversationState = conversationState || new ConversationState(new MemoryStorage());

let dialogState = this.conversationState.createProperty('DialogState');

const targetDialogs = [
bot.dialogSet.find(dialogToTest),
bot.dialogSet.find(dialogToTest + '_default_prompt'),
bot.dialogSet.find(dialogToTest + ':botkit-wrapper'),
];

this._callback = this.getDefaultCallback(targetDialogs, initialDialogOptions || null, dialogState);

if (typeof channelOrAdapter == 'string') {
this._testAdapter = new TestAdapter(this._callback, {channelId: channelOrAdapter}).use(new AutoSaveStateMiddleware(this.conversationState));
} else {
this._testAdapter = channelOrAdapter;
}

this.addUserMiddlewares(middlewares);
}

/**
* Send an activity into the dialog.
* @returns a TestFlow that can be used to assert replies etc
* @param activity an activity potentially with text
*
* ```javascript
* DialogTest.send('hello').assertReply('hello yourself').then(done);
* ```
*/
public async sendActivity(activity: Partial<Activity> | string): Promise<any> {
await this._testAdapter.receiveActivity(activity);
return this._testAdapter.activityBuffer.shift();
}

/**
* Get the next reply waiting to be delivered (if one exists)
*/
public getNextReply() {
return this._testAdapter.activityBuffer.shift();
}

private getDefaultCallback(targetDialogs: Dialog[], initialDialogOptions: any, dialogState: any): (turnContext: TurnContext) => Promise<void> {

return async (turnContext: TurnContext) => {

const dialogSet = new DialogSet(dialogState);
targetDialogs.forEach(targetDialog => dialogSet.add(targetDialog));

const dialogContext = await dialogSet.createContext(turnContext);
this.dialogTurnResult = await dialogContext.continueDialog();
if (this.dialogTurnResult.status === DialogTurnStatus.empty) {
this.dialogTurnResult = await dialogContext.beginDialog(targetDialogs[0].id, initialDialogOptions);
}
};
}

private addUserMiddlewares(middlewares: Middleware[]): void {
if (middlewares != null) {
middlewares.forEach((middleware) => {
this._testAdapter.use(middleware);
});
}
}

}
2 changes: 1 addition & 1 deletion packages/botkit/tests/Core.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ const { Botkit } = require('../');
describe('Botkit', function() {

it('should create a Botkit controller', function () {
assert((new Botkit({}) instanceof Botkit), 'Botkit is wrong type');
assert((new Botkit({ disable_webserver: true }) instanceof Botkit), 'Botkit is wrong type');
});
});
34 changes: 34 additions & 0 deletions packages/botkit/tests/Dialog.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const assert = require('assert');
const { Botkit, BotkitTestClient, BotkitConversation } = require('../');

let bot;

describe('Botkit dialog', function() {
beforeEach(async () => {
bot = new Botkit();
});

it('should follow a dialog', async function () {
const introDialog = new BotkitConversation('introduction', bot);
introDialog.ask({
text: 'You can say Ok',
quick_replies: [{
title: 'Ok',
payload: 'Ok'
}],
}, [], 'continue');
bot.addDialog(introDialog);

// set up a test client
const client = new BotkitTestClient('test', bot, 'introduction');

// Get details for the reply
const quickreply_reply = await client.sendActivity();
assert(quickreply_reply.text === 'You can say Ok');
assert(quickreply_reply.channelData.quick_replies[0].title === 'Ok');
});

afterEach(async () => {
await bot.shutdown();
});
});