@telefonica/la-bot-testing
v2.0.1
Published
Living Apps Bot Testing Library
Downloads
23
Readme
la-bot-sdk
Testing Library for Living Apps bots
1. Usage
1.1. Install
Set up the dependencies in package.json
as follows:
"devDependencies": {
"@telefonica/la-bot-testing": "^1.0.0-alpha.1",
"botbuilder-testing": "~4.6.2"
}
Now run npm install
.
1.2. Initialization
The Living App testing library needs to be initialized prior to any test launch. The best way to do this is with a beforeEach hook where the beginTesting function is called. This function takes as parameters:
- an object with partial LivingApp info, at least having the livingApp name and library name, which are required to correctly initialize the dialogs
- an optional user object, in case you want to configure specific user data
import * as sdk from '@telefonica/la-bot-testing';
beforeEach(() => {
sdk.beginTesting({ name: 'your-living-app', libraryName: 'your-living-app' });
});
This will allow your tests to be default-initialized and ready for normal dialog flow.
1.3 Normal Testing
import { Channel } from '@telefonica/la-bot-testing';
import * as sdk from '@telefonica/la-bot-testing';
import { DialogTestClient } from 'botbuilder-testing';
import MyDialog from '../../src/dialogs/dialog';
import { ChoiceOperation, RedirectIntent, Screen, SessionData } from '../../src/models';
describe('MyDialog dialog', () => {
test('MyDialog - no entity & no persistence', async (done) => {
// Load the dialog with env vars from settings/.env; This can be overrided
// with specific config entries:
// { ...sdk.loadConfig('settings/.env), MY_LIVING_APP_CONFIG_ENTRY: "mock://host" }
const dialog = new MyDialog(sdk.loadConfig('settings/.env'));
const client = new DialogTestClient('test', dialog, {});
let messages;
// We set the intent and entities (if any) that should lead to this dialog
sdk.setIntent({
intent: RedirectIntent.HOME,
entities: [],
});
// We set the active channel, the one that would have initialized the request
sdk.setActiveChannel(Channel.STBH);
await client.sendActivity('');
// The dialog has been executed until the first prompt, we can process the
// messages per channel and assert anything we want
messages = sdk.getChannelMessages(Channel.STBH);
expect(messages).toHaveLength(1);
messages = sdk.getChannelMessages(Channel.MH);
expect(messages).toHaveLength(1);
// We can do the same with the SessionData contents
const sessionData = sdk.getSessionData() as SessionData;
expect(sessionData.currentOffer).toBe(0);
expect(sessionData.origin).toBeUndefined();
// And with the cross-session persistence too
const persistence = sdk.getPersistenceData();
expect(persistence.origin).toBeUndefined();
done();
});
}
1.4 Advanced Features
There are several advanced features available in case normal flow is not enough.
Channel Configuration
- function setActiveChannel(channel: Channel): void;
- As we have seen in the normal example, this sets the requesting channel
- function resetChannels(): void;
- This clears the channels from the session, so there are no active channels joined
- function joinChannel(channel: Channel, state: State = State.STABLE): void;
- This sets an active channel in one of three possible states: JOINING (useful when testing a start dialog), JOINED (useful when testing what happens when a channel has just joined), STABLE (useful when you do not want to test joining of channels). Remember that a JOINED channel only sends the first message after joining to itself, since this is the joining dispatcher behaviour.
- function removeChannel(channel: Channel): void;
- Removes a specific channel from the session.
- function getChannelActivities(name: Channel): Partial[];
- Allows you to get the full activities instead of the la-data attachment
Logging configuration
- function setLoggingOutput(state: boolean): void;
- Activates logging output to console (default offline)
- function getBotLogs(): string[];
- Gives you any log generated by the dialog
Session handling
- function getSession(): DeepPartial;
- This will allow you to modify the session object, which usually is not a good idea.
Persistence and SessionData reset
- function clearSessionData(): void;
- function clearPersistenceData(): void
Example: Start Dialog testing
import { Channel, State } from '@telefonica/la-bot-testing';
import * as sdk from '@telefonica/la-bot-testing';
import { DialogTestClient } from 'botbuilder-testing';
import MyLivingAppStartDialog from '../../src/dialogs/dialog-la-start';
beforeEach(() => {
sdk.beginTesting({ name: 'your-living-app', libraryName: 'your-living-app' });
});
describe('Start dialogs', () => {
test('stbh => mh', async (done) => {
const dialog = new MyLivingAppStartDialog({});
const client = new DialogTestClient('test', dialog, {});
let messages, activity;
sdk.resetChannels();
sdk.setIntent({
intent: 'intent.internal.living-app.start',
entities: [{ type: 'ent.living_app_name', entity: 'your-living-app' }],
});
// First channel to join will be STBH
sdk.setActiveChannel(Channel.STBH);
sdk.joinChannel(Channel.STBH, State.JOINING);
await client.sendActivity('');
// Only STBH should have message
messages = sdk.getChannelMessages(Channel.STBH);
expect(messages).toHaveLength(1);
expect(messages[0]).toHaveProperty('activeChannels', [Channel.STBH]);
messages = sdk.getChannelMessages(Channel.MH);
expect(messages).toHaveLength(0);
// Only STBH should have activity and no text/html attachment
activity = sdk.getChannelActivities(Channel.STBH)[0];
expect(activity.attachments.find((att) => att.contentType === 'text/html')).toBeUndefined();
activity = sdk.getChannelActivities(Channel.MH)[0];
expect(activity).toBeUndefined();
// We clear the messages so we don't have complexity when asserting them
sdk.clearMessages();
// Now MH joins the session
sdk.setActiveChannel(Channel.MH);
sdk.joinChannel(Channel.MH, State.JOINING);
await client.sendActivity('');
// Only MH should have message
messages = sdk.getChannelMessages(Channel.MH);
expect(messages).toHaveLength(1);
expect(messages[0]).toHaveProperty('activeChannels', [Channel.STBH, Channel.MH]);
messages = sdk.getChannelMessages(Channel.STBH);
expect(messages).toHaveLength(0);
// Only MH shoudl have activity, but with no text/html attachment, since STBH is already joined
activity = sdk.getChannelActivities(Channel.MH)[0];
expect(activity.attachments.find((att) => att.contentType === 'text/html')).toBeUndefined();
activity = sdk.getChannelActivities(Channel.STBH)[0];
expect(activity).toBeUndefined();
done();
});
test('mh => stbh', async (done) => {
const dialog = new MyLivingAppStartDialog({});
const client = new DialogTestClient('test', dialog, {});
let messages, activity;
sdk.resetChannels();
sdk.setIntent({
intent: 'intent.internal.living-app.start',
entities: [{ type: 'ent.living_app_name', entity: 'your-living-app' }],
});
sdk.setActiveChannel(Channel.MH);
sdk.joinChannel(Channel.MH, State.JOINING);
await client.sendActivity('');
messages = sdk.getChannelMessages(Channel.MH);
expect(messages).toHaveLength(1);
expect(messages[0]).toHaveProperty('activeChannels', [Channel.MH]);
messages = sdk.getChannelMessages(Channel.STBH);
expect(messages).toHaveLength(0);
// In this case, since MH joins first, the text/html attachment IS defined
activity = sdk.getChannelActivities(Channel.MH)[0];
expect(activity.attachments.find((att) => att.contentType === 'text/html')).toHaveProperty('contentUrl');
activity = sdk.getChannelActivities(Channel.STBH)[0];
expect(activity).toBeUndefined();
sdk.clearMessages();
sdk.setActiveChannel(Channel.STBH);
sdk.joinChannel(Channel.STBH, State.JOINING);
await client.sendActivity('');
messages = sdk.getChannelMessages(Channel.STBH);
expect(messages).toHaveLength(1);
expect(messages[0]).toHaveProperty('activeChannels', [Channel.MH, Channel.STBH]);
messages = sdk.getChannelMessages(Channel.MH);
expect(messages).toHaveLength(0);
activity = sdk.getChannelActivities(Channel.MH)[0];
expect(activity).toBeUndefined();
activity = sdk.getChannelActivities(Channel.STBH)[0];
expect(activity.attachments.find((att) => att.contentType === 'text/html')).toBeUndefined();
done();
});
});