スポンサードサーチ
サンプルコード
サンプルとして、
以下のようなチャンネルに(ボット, アプリ以外の)ユーザーがいるかを確認するコードを書いたとする
import { WebClient } from '@slack/web-api';
const getClient = (token: string) => {
return new WebClient(token);
};
const getSlackChannelInfo = async (slackChannelId: string) => {
try {
const client = getClient(process.env.BOT_TOKEN );
return await client.conversations.info({
channel: slackChannelId
});
} catch (error) {
console.log(`Failed to get slack channel info: ${error.message}`);
throw error;
}
};
const getSlackChannelMembers = async (slackChannelId: string) => {
try {
const client = getClient(process.env.BOT_TOKEN );
return await client.conversations.members({
channel: slackChannelId
});
} catch (error) {
console.log(`Failed to get slack channel members: ${error.message}`);
throw error;
}
};
const getSlackUserInfo = async (slackUserId: string) => {
try {
const client = getClient(process.env.BOT_TOKEN );
return await client.users.info({
user: slackUserId
});
} catch (error) {
console.log(`Failed to get slack user: ${error.message}`);
return null;
}
};
const postMessageToSlackChannel = async (
message: string,
slackChannelId: string
) => {
try {
const client = getClient(process.env.BOT_TOKEN );
const params = {
text: message,
channel: slackChannelId
};
await client.chat.postMessage(params);
} catch (error) {
console.log(`Failed to send slack message: ${error.message}`);
throw error;
}
};
const isChannelActiveAndHasMembers = async (slackChannelId: string) => {
try {
const slackChannelInfo = await getSlackChannelInfo(channelId);
const channel = slackChannelInfo?.channel;
if (!channel || channel.is_archived) {
return false;
}
const slackChannelMembers = await getSlackChannelMembers(slackChannelId);
const slackUserIds = slackChannelMembers?.members || [];
for (const slackUserId of slackUserIds) {
const slackUserInfo = await getSlackUserInfo(slackUserId);
console.log('slackUserInfo', slackUserInfo);
const user = slackUserInfo?.user;
if (user && !user.is_bot && !user.is_app_user && !user.deleted) {
return true;
}
}
return false;
} catch (error) {
return false;
}
}
const sample = async (slackChannelId: string) => {
const hasHumanUsers = await isChannelActiveAndHasMembers(slackChannelId);
if (!hasHumanUsers ) {
await postMessageToSlackChannel('エラーです', process.env.NOTICE_SLACK_CHANNEL_ID);
return true;
}
return false;
}
テストコード
import { WebClient } from '@slack/web-api';
jest.mock('@slack/web-api', () => {
return {
WebClient: jest.fn(() => ({
chat: {
postMessage: jest.fn()
},
conversations: {
info: jest.fn(),
members: jest.fn()
},
users: {
info: jest.fn()
}
}))
};
});
const mockedWebClient = WebClient as unknown as jest.Mock;
const testSlackChannelId = 'C0xxxxxxxxx';
describe('サンプルテスト', () => {
let mockSlackPostMessage: jest.Mock;
let mockSlackChannelInfo: jest.Mock;
let mockSlackChannelMembers: jest.Mock;
let mockedSlackUserInfo: jest.Mock;
beforeEach(async () => {
process.env.BOT_TOKEN = 'dummy-token';
mockSlackPostMessage = jest.fn().mockResolvedValue({ ok: true });
mockSlackChannelInfo = jest
.fn()
.mockResolvedValue({ ok: true, channel: { is_archived: false } });
mockSlackChannelMembers = jest.fn().mockResolvedValue({
ok: true,
members: ['mockUserId']
});
mockedSlackUserInfo = jest.fn().mockResolvedValue({
ok: true,
user: {
is_bot: false,
is_app_user: false,
deleted: false
}
});
mockedWebClient.mockImplementation(() => ({
chat: { postMessage: mockSlackPostMessage },
conversations: {
info: mockSlackChannelInfo,
members: mockSlackChannelMembers
},
users: { info: mockedSlackUserInfo }
}));
});
it('正常系', async () => {
await sample(testSlackChannelId);
expect(mockSlackPostMessage).not.toHaveBeenCalled();
});
it('Slackチャンネルの情報が取得できない場合', async () => {
mockSlackChannelInfo.mockRejectedValueOnce(
new Error('conversations.info error')
);
await sample(testSlackChannelId);
expect(mockSlackPostMessage).toHaveBeenCalledWith(
expect.objectContaining({
channel: process.env.NOTICE_SLACK_CHANNEL_ID
})
);
});
it('Slackチャンネルがアーカイブな場合', async () => {
mockSlackChannelInfo.mockRejectedValueOnce({
ok: true,
is_archived: true
});
await sample(testSlackChannelId);
expect(mockSlackPostMessage).toHaveBeenCalledWith(
expect.objectContaining({
channel: process.env.NOTICE_SLACK_CHANNEL_ID
})
);
});
it('Slackチャンネルにユーザーがいない場合', async () => {
mockedSlackUserInfo = jest.fn().mockResolvedValue({
ok: true,
user: {
is_bot: true,
is_app_user: false,
deleted: false
}
});
await sample(testSlackChannelId);
expect(mockSlackPostMessage).toHaveBeenCalledWith(
expect.objectContaining({
channel: process.env.NOTICE_SLACK_CHANNEL_ID
})
);
});
});
ざっくり解説
- WebClientをjest.Mockにするために、
as unknown
を挟む
const mockedWebClient = WebClient as unknown as jest.Mock;
- デフォルトのレスポンスを用意しておく
mockedWebClient.mockImplementation(() => ({
chat: { postMessage: mockSlackPostMessage },
conversations: {
info: mockSlackChannelInfo,
members: mockSlackChannelMembers
},
users: { info: mockedSlackUserInfo }
}));
- テスト内容に応じてレスポンス内容を書き換える
mockSlackChannelInfo.mockRejectedValueOnce(
new Error('conversations.info error')
);
mockSlackChannelInfo.mockRejectedValueOnce({
ok: true,
is_archived: true
});
mockedSlackUserInfo = jest.fn().mockResolvedValue({
ok: true,
user: {
is_bot: true,
is_app_user: false,
deleted: false
}
});
- 固定値での実行が決まっていれば、それで渡されているかを確認
Slack APIというよりはJestの書き方の話だけれど、下記の場合はclient.chat.postMessage
のchannelが環境変数値で決まっいたパターン
expect(mockSlackPostMessage).toHaveBeenCalledWith(
expect.objectContaining({
channel: process.env.NOTICE_SLACK_CHANNEL_ID
})
);