これは、なにをしたくて書いたもの?
前に、Node.jsでEcho Server/Clientを書いてみたのですが。
Node.jsで、Echo Client/Serverを書いてみる - CLOVER🍀
今回は、こちらをTypeScriptに書き換えつつ、テストコードも書いてみようかなと思います。
環境
今回の環境は、こちら。
$ node --version v16.13.0 $ npm --version 8.1.0
準備
まずは、TypeScriptプロジェクトの準備。Jestのインストールも合わせて。
$ npm init -y $ npm i -D typescript $ npm i -D -E prettier $ npm i -D jest @types/jest ts-jest $ npx ts-jest config:init $ mkdir src test
tsconfig.json
{ "compilerOptions": { "target": "esnext", "module": "commonjs", "baseUrl": "./src", "outDir": "dist", "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true, "noPropertyAccessFromIndexSignature": true, "esModuleInterop": true }, "include": [ "src" ] }
.prettierrc.json
{ "singleQuote": true }
jest.config.js
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { preset: 'ts-jest', testEnvironment: 'node', };
Node.jsの型宣言もインストール。
$ npm i -D @types/node
バージョンはこうなりました。
"devDependencies": { "@types/jest": "^27.0.3", "@types/node": "^16.11.9", "jest": "^27.3.1", "prettier": "2.4.1", "ts-jest": "^27.0.7", "typescript": "^4.5.2" }
サーバーを書く
まずは、サーバーを書いていきます。テストコードで動かすことを考えて、サーバーの定義と起動は別々にしておきます。
クライアントと共通で使うログ出力用関数。
src/logger.ts
export const logger = (fun: () => any) => console.log(`[${new Date().toISOString()}] ${fun()}`);
サーバー定義。
src/server.ts
import { Server, createServer } from 'net'; import { logger } from './logger'; function newServer(): Server { const server = createServer((socket) => { const clientAddress = `${socket.remoteAddress}:${socket.remotePort}`; logger(() => `connect client = ${clientAddress}`); socket.setEncoding('utf8'); socket.on('data', (data: string) => { const trimmedData = data.trim(); logger(() => `received data[${trimmedData}]`); socket.write(`★${trimmedData}★`); }); socket.on('close', () => logger(() => `disconnect client = ${clientAddress}`) ); }); server.on('error', (err) => { throw err; }); server.on('close', () => logger(() => 'echo server, shutdown')); return server; } export const server = newServer();
送られてきたメッセージに、「★」を付けて返すことにします。
socket.write(`★${trimmedData}★`);
起動する部分。
src/runServer.ts
import { logger } from './logger'; import { server } from './server'; const address = 'localhost'; const port = 3000; const options = { host: address, port: port, }; server.listen(options, () => logger(() => `echo server[${address}:${port}], startup`) );
確認してみましょう。
$ npx tsc
起動。
$ node dist/runServer.js [2021-11-20T12:56:22.235Z] echo server[localhost:3000], startup
確認。
$ echo hello | nc localhost 3000 ★hello★ $ echo world | nc localhost 3000 ★world★
サーバー側のログ。
[2021-11-20T12:56:46.182Z] connect client = 127.0.0.1:59306 [2021-11-20T12:56:46.183Z] received data[hello] [2021-11-20T12:56:49.068Z] disconnect client = 127.0.0.1:59306 [2021-11-20T12:57:05.967Z] connect client = 127.0.0.1:59348 [2021-11-20T12:57:05.968Z] received data[world] [2021-11-20T12:57:07.053Z] disconnect client = 127.0.0.1:59348
クライアント側
続いて、クライアント側を書いていきます。
クライアント定義。
src/client.ts
import net from 'net'; import { logger } from './logger'; export function sendMessage( address: string, port: number, message: string ): Promise<string> { return new Promise((resolve) => { const client = net.createConnection({ host: address, port: port }, () => logger(() => `start client`) ); client.setEncoding('utf8'); client.on('connect', () => { logger(() => `connected server[${address}:${client.remotePort}]`); client.write(message); logger(() => `send message[${message}]`); }); client.on('data', (data: string) => { resolve(data); client.end(); client.destroy(); }); }); }
起動部分。起動時の引数で、送信するメッセージや送信先を指定できるようにしています。
src/runClient.ts
import { sendMessage } from './client'; import { logger } from './logger'; const args = process.argv.slice(2); let address; let port; let message; if (args.length > 2) { address = args[0]; port = parseInt(args[1]); message = args[2]; } else if (args.length > 1) { address = 'localhost'; port = parseInt(args[0]); message = args[1]; } else if (args.length == 1) { address = 'localhost'; port = 3000; message = args[0]; } else { address = 'localhost'; port = 3000; message = 'Hello World'; } sendMessage(address, port, message).then((message) => logger(() => `receive message[${message}]`) );
ビルドして
$ npx tsc
引数別に確認。サーバー側は起動したままとします。
$ node dist/runClient.js [2021-11-20T13:13:28.345Z] start client [2021-11-20T13:13:28.347Z] connected server[localhost:3000] [2021-11-20T13:13:28.347Z] send message[Hello World] [2021-11-20T13:13:28.349Z] receive message[★Hello World★] $ node dist/runClient.js TypeScript [2021-11-20T13:13:40.328Z] start client [2021-11-20T13:13:40.331Z] connected server[localhost:3000] [2021-11-20T13:13:40.331Z] send message[TypeScript] [2021-11-20T13:13:40.333Z] receive message[★TypeScript★] $ node dist/runClient.js 3000 TypeScript [2021-11-20T13:13:43.449Z] start client [2021-11-20T13:13:43.450Z] connected server[localhost:3000] [2021-11-20T13:13:43.451Z] send message[TypeScript] [2021-11-20T13:13:43.453Z] receive message[★TypeScript★] $ node dist/runClient.js 127.0.0.1 3000 TypeScript [2021-11-20T13:14:01.253Z] start client [2021-11-20T13:14:01.257Z] connected server[127.0.0.1:3000] [2021-11-20T13:14:01.257Z] send message[TypeScript] [2021-11-20T13:14:01.259Z] receive message[★TypeScript★]
OKですね。
これで、いったんサーバー側も停止しておきます。
テストを書く
最後に、テストコードを書いて確認します。
test/echo.test.ts
import { server } from '../src/server'; import { sendMessage } from '../src/client'; const address = 'localhost'; const port = 3001; beforeAll( () => new Promise((resolve) => server.listen({ host: address, port: port }, () => resolve('start')) ) ); test('echo, Hello World', async () => { const reply = await sendMessage(address, port, 'Hello World'); expect(reply).toBe('★Hello World★'); }); test('echo, こんにちは、世界', async () => { const reply = await sendMessage(address, port, 'こんにちは、世界'); expect(reply).toBe('★こんにちは、世界★'); }); afterAll(() => new Promise((resolve) => server.close(() => resolve('end'))));
テスト開始時にサーバーを起動して、終了時に停止するようにしておきました。
確認。
$ npx jest
テスト自体は成功します。
PASS test/echo.test.ts ✓ echo, Hello World (34 ms) ✓ echo, こんにちは、世界 (8 ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 2.321 s, estimated 3 s Ran all test suites.
ただ、ログがたくさん出るのと
console.log [2021-11-20T13:22:21.193Z] connect client = 127.0.0.1:55074 at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.213Z] start client at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.216Z] connected server[localhost:3001] at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.217Z] send message[Hello World] at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.219Z] received data[Hello World] at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.225Z] connect client = 127.0.0.1:55076 at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.226Z] start client at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.227Z] connected server[localhost:3001] at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.228Z] send message[こんにちは、世界] at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.229Z] disconnect client = 127.0.0.1:55074 at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.230Z] received data[こんにちは、世界] at logger (src/logger.ts:2:11) console.log [2021-11-20T13:22:21.232Z] echo server, shutdown at logger (src/logger.ts:2:11)
最後にテスト後に出力しようとしたログが、警告されたり。
● Cannot log after tests are done. Did you forget to wait for something async in your test? Attempted to log "[2021-11-20T13:22:21.243Z] disconnect client = 127.0.0.1:55076". 1 | export const logger = (fun: () => any) => > 2 | console.log(`[${new Date().toISOString()}] ${fun()}`); | ^ 3 | at console.log (node_modules/@jest/console/build/CustomConsole.js:187:10) at logger (src/logger.ts:2:11) at Socket.<anonymous> (src/server.ts:20:13)
async
を使っているからですね。
まとめ
前にNode.jsで書いたEcho Server/Clientを、TypeScriptで書き直しつつ、テストコードも付けてみました。
まだまだTypeScriptに慣れないので、こういうのを繰り返さないとですねぇ。