見出し画像

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にお願いしたら、足りていなかったエラーハンドリングのルーチンを教えてくれたんですよね。

見てください。この彼の自信満々なメッセージを。

自信満々な ChatGPT

実際、そのコードに置き換えて無事にテストが通ってしまったんですけど、ずっと面倒で書かなかったテストコードをこうやっていともたやすく準備してくれるのは、本当にありがたいです。素晴らしい。


貴方がサポートしてくれると、私が幸せ。 私が幸せになると、貴方も幸せ。 新しいガジェット・ソフトウェアのレビューに、貴方の力が必要です。