ChatGPTにJest+Supertestなテストコードを準備させてみた
すぱっとテストコード書いてくれて、とても便利すぎます。感動的です。
ただ、ファイルの分割度合によっては、エスパーのようにくみ取ってはくれないので、特定のファイルだけでは正常に動作しないケースもあります。
今回、Expressフレームワークを使ったJavaScriptのテスト用のサイトの、ルーティング部分のテストを行うコードをChatGPTさんに作ってもらいました。
元のJavaScriptのコードはこちらの /routes/index.js です。特定のホスト名ごとに res.render で表示するサイトを切り替えるというだけです。宛先もただ画像が表示されるだけです。
const express = require('express');
const router = express.Router();
router.get('/', function (req, res, next) {
const hostname = req.headers.host;
switch (hostname) {
case 'localhost':
res.render('local', { title: 'access to localhost' });
break;
case 'prfm.info':
res.render('prfm', { title: 'access to Perfume' });
break;
case 'multi.kashiyuka.info':
res.render('kashiyuka', { title: 'access to kashiyuka' });
break;
case 'multi.prfm.jp':
res.render('index', { title: 'access to prfm.jp' });
break;
default:
let err = new Error('host not found');
err.status = 400;
next(err);
break;
}
});
module.exports = router;
大元はこちらです。この app.js から、次のコードあたりで定義され呼ばれる仕組みとなっています。
const indexRouter = require('./routes/index');
:
app.use('/', indexRouter);
const express = require('express');
const path = require('path');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const { json, urlencoded } = require('body-parser');
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const app = express();
// View engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Middleware
app.use(logger('dev'));
app.use(json());
app.use(urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.use('/', indexRouter);
app.use('/users', usersRouter);
// Catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
// Error handler
app.use((err, req, res, next) => {
// Set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// Render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
先ほどの /routes/index.js を Jest と Supertest を使ってテストコードを出力してください、と ChatGPTにお願いして出力してもらったテストコードは次の通りです。
const request = require('supertest');
const express = require('express');
const app = express();
const router = require('./app');
describe('Routing', () => {
test('GET / with localhost should render local template', async () => {
const response = await request(app).get('/').set('host', 'localhost');
expect(response.status).toBe(200);
expect(response.text).toContain('access to localhost');
});
test('GET / with prfm.info should render prfm template', async () => {
const response = await request(app).get('/').set('host', 'prfm.info');
expect(response.status).toBe(200);
expect(response.text).toContain('access to Perfume');
});
test('GET / with multi.kashiyuka.info should render kashiyuka template', async () => {
const response = await request(app).get('/').set('host', 'multi.kashiyuka.info');
expect(response.status).toBe(200);
expect(response.text).toContain('access to kashiyuka');
});
test('GET / with multi.prfm.jp should render index template', async () => {
const response = await request(app).get('/').set('host', 'multi.prfm.jp');
expect(response.status).toBe(200);
expect(response.text).toContain('access to prfm.jp');
});
test('GET / with unknown host should return 400 error', async () => {
const response = await request(app).get('/').set('host', 'unknown.host');
expect(response.status).toBe(400);
});
});
うん。それっぽい。が、しかし残念ながら動きません。app.js で Express がルーティングを定義していることや、ファイル名を指示していないので、require するファイルも間違っています。これは私の責任です。指示不足です。
また、Express フレームワークを使うことを前提として伝えていますが、エラーハンドリングや view engine を指定しておかないと、テストは成功しません。残念。
const request = require('supertest');
const express = require('express');
const app = express();
const router = require('../routes/index');
// need to declare "view engine" for using 'res.render'
app.set("view engine", "ejs");
// Register error handling middleware
router.use((err, req, res, next) => {
res.status(err.status || 500);
res.send(err.message);
});
describe('Routing', () => {
test('GET / with localhost should render local template', async () => {
const response = await request(app).get('/').set('host', 'localhost');
expect(response.status).toBe(200);
expect(response.text).toContain('access to localhost');
});
test('GET / with prfm.info should render prfm template', async () => {
const response = await request(app).get('/').set('host', 'prfm.info');
expect(response.status).toBe(200);
expect(response.text).toContain('access to Perfume');
});
test('GET / with multi.kashiyuka.info should render kashiyuka template', async () => {
const response = await request(app).get('/').set('host', 'multi.kashiyuka.info');
expect(response.status).toBe(200);
expect(response.text).toContain('access to kashiyuka');
});
test('GET / with multi.prfm.jp should render index template', async () => {
const response = await request(app).get('/').set('host', 'multi.prfm.jp');
expect(response.status).toBe(200);
expect(response.text).toContain('access to prfm.jp');
});
test('GET / with unknown host should return 400 error', async () => {
const response = await request(app).get('/').set('host', 'unknown.host');
expect(response.status).toBe(400);
});
});
修正と追記個所は次の部分です。
const router = require('../routes/index');
// need to declare "view engine" for using 'res.render'
app.set("view engine", "ejs");
// Register error handling middleware
router.use((err, req, res, next) => {
res.status(err.status || 500);
res.send(err.message);
});
いやでも凄いっすよ。何がって、require はすぐに分かりましたけれども "view engine"指定しなきゃダメかみたいなのはすぐ気がつきませんでした。これは ChatGPT でも分かってくれませんでした。
実はエラーハンドリングのルーチン入れなくても、私のローカルではテストうまくいってました。ただ、Heroku CI でテストしたら
Did you forget to wait for something async in your test?
Attempted to log "Error: host not found
とか出てきて、エラー停止する始末。わたしの CI/CD 自動化の夢がついえるかと思いましたわ。もう今日はほとんど寝ていなかったので、先ほどの routes/index.js とテストコードを全部食わして、このエラーでたんだけど直してって ChatGPTにお願いしたら、足りていなかったエラーハンドリングのルーチンを教えてくれたんですよね。
見てください。この彼の自信満々なメッセージを。
実際、そのコードに置き換えて無事にテストが通ってしまったんですけど、ずっと面倒で書かなかったテストコードをこうやっていともたやすく準備してくれるのは、本当にありがたいです。素晴らしい。