【Expo SQLite + TypeORM】Jestでexpo-sqliteの接続テストが書けなかった話

f:id:kaminashi-developer:20210228213539j:plain

こんにちは、株式会社カミナシのエンジニア @ImuKnskです。

はじめに

みなさんのプロダクトでテスト書いてますか?

『カミナシ』はオフライン機能を提供しており、ローカルデータベースはSQLite(expo-sqlite)を利用しています。

オフライン機能って何?という話は、こちらの記事をご参照ください。

kaminashi-developer.hatenablog.jp

SQLiteに保存されるデータは『カミナシ』にとって重要なデータが保存されています。 そのため万が一にでもデータを欠損してはいけないため、テストを充実させていきたいと思っています。

まずはCRUD処理を簡単に書いてみようと思ったのですが、SQLite(expo-sqlite)+ TypeORMで接続のテストが書けずに困った話をしようと思います。(これを解決出来たらCRUD処理を書きたい)

もし解決策を知っている方がいたら教えていただけると幸いです。

↓↓↓ 以下は試した環境を書いています ↓↓↓

前準備@インストール編

expoをインストール
npm install -g expo-cli
テンプレートを作成

今回はManaged workflowのblank(TypeScript)を利用します。

expo int sqlite_test

f:id:kaminashi-developer:20210214140137p:plain

しばらくするとプロジェクトが作成されます! f:id:kaminashi-developer:20210214140404p:plain

SQLiteをインストール

expo-sqliteでreact-native-sqlite-storageを利用しているのでインストールします。

yarn add expo-sqlite@~8.5.0 react-native-sqlite-storage@5.0.0 
TypeScriptのORマッパー

SQLiteのDB操作をするためにTypeORMを利用します。

github.com

yarn add github:cuibonobo/typeorm-package reflect-metadata --dev

tsconfig.json で以下の設定を有効にします。

"emitDecoratorMetadata": true
"experimentalDecorators": true
テストフレームワークのインストール

Expoガイドに載っているJestを利用します。

docs.expo.io

yarn add jest-expo @types/jest --dev

テストファイルには以下のインポートを追加しましょう。(TypeORMを使わない場合は不要です)

hoge.test.ts

import 'reflect-metadata'

package.jsonに以下の設定を追加してください。

"scripts": {
  ...
  "test": "jest"
},
"jest": {
  "preset": "jest-expo"
}

前準備@データベース作成編

TypeORMを使ってユーザーテーブルのEntityを作成

注意したいのがSQLiteを利用するので、Columnに指定するtypeがSQLiteの定義になっていることを確認してください。定義が間違っている場合はwarningになってしまいます。

sqlite/entities/user.ts

import { Entity, PrimaryColumn, Column, CreateDateColumn } from 'typeorm'

@Entity()
export class EntityTestUser {
  // Primaryの数値型
  @PrimaryColumn({ type: 'integer' })
  id?: number

  // 100文字まで保存
  @Column({ type: 'text', length: 100 })
  name?: string

  // 数値型でNULLを許可する
  @Column({ type: 'integer', nullable: true })
  age?: number

  // 作成日の初期値はdefaultで予め定義しておきます
  @CreateDateColumn({ name: 'created_at', precision: 0, default: () => `DATETIME('now', 'localtime')` })
  readonly createdAt?: string
}
データベース接続処理を作成

Entityを作成したあとは、データベースの接続設定を行いましょう。 今回はユニットテストで使うため、database:memory:とします。

sqlite/types/database.ts

// データベース名
export const DATABASE_NAME = ':memory:'
// コネクション名
export const CONNECTION_NAME = 'sample'

sqlite/connection.ts

import { createConnection } from 'typeorm'
import { EntityTestUser } from './entities/user'
import { DATABASE_NAME, CONNECTION_NAME } from './types/database'

const createConnectionSQLite = async () => {
  return await createConnection({
    database: DATABASE_NAME,
    name: CONNECTION_NAME,
    // expoのsqliteを利用する
    type: 'expo',
    driver: require('expo-sqlite'),
    // 利用するテーブル一覧(entitiesに追加しないと対象とならない点に注意してください)
    entities: [EntityTestUser],
    // createConnectionしたときにentitiesのテーブルを作成する(falseの場合は自動生成しません)
    synchronize: true,
    // queryのログを出力
    logging: ['query']
  })
}

export default createConnectionSQLite

この例ではexpo-sqliteドライバを利用したexpoデータベースを利用します。 別のデータベースを利用する場合はtypemysql, postgres, react-native等を利用できます。その際は各データベースの設定にホストやポート等を変更してください。

SQLiteのテスト書いてみよう

これで前準備はバッチリですね!早速テストを書いてみましょう!

sqlite/connection.test.ts

import createConnectionSQLite from './connection'
import 'reflect-metadata'

describe('expo-sqlite testing', () => {
  test('exist connection', async () => {
    const connection = await createConnectionSQLite()
    expect(connection).not.toBe(undefined)
  })
})

接続が出来ていればテストが成功するので、yarn test を実行してみましょう!

% yarn test
yarn run v1.19.1
$ jest
 FAIL  sqlite/connection.test.ts
  expo-sqlite testing
    ✕ exist connection (362ms)

  ● expo-sqlite testing › exist connection

    TypeError: Cannot read property 'map' of undefined

      at node_modules/expo-sqlite/src/SQLite.ts:27:41

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        3.073s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

ムムム…。華麗にコケました。

エラー箇所のコードを確認したところ、expo-sqliteSQLiteDatabase.exec でエラーになっていました。 thenで正常に終わっているが、nativeResultSetsに入っていないようです。 f:id:kaminashi-developer:20210223145150p:plain

ここから数時間近くハマって解決策が見つかっていません...。

確認したことは

  • createConnectionのプロパティでdatabaseにmemoryが利用できないのか
    • App.tsxに記述して正常に動作しているため、memoryの問題ではなかった
    • この関数自体は正常に動作している
  • TypeORM, ExpoのGithubのissue
    • 似たようなissueがあったけどenumは使っていなかった github.com

もしかして、Jestのテストで利用出来ないのでは?と思い、素のSQLiteを使ってみました。

まずはSQLite接続用のドライバーが別途必要なためインストールを行います。

yarn add sqlite3 --dev  

sqlite/connection.ts

const createConnectionSQLite = async () => {
  return await createConnection({
    database: DATABASE_NAME,
    name: CONNECTION_NAME,
    type: 'sqlite',
    entities: [EntityTestUser],
    synchronize: true
  })
}

再度 yarn test を実行してみましょう!

% yarn test
yarn run v1.19.1
$ jest
 PASS  sqlite/connection.test.ts
  expo-sqlite testing
    ✓ exist connection (311ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.739s
Ran all test suites.
✨  Done in 5.89s.

テストが通りました!

SQLiteの問題ではなく、expo-sqliteを使ったテストが動かないということだと思います。これが環境問題なのか、設定不足なのかが現時点では分かっていない状態です。

分かっていないこと

  • TypeORMのcreateConnectionでexpo-sqliteのドライバを使ったテストの書き方が分からない!

解決策を知っている方がいたら、コメント等で教えていただけると幸いです。

おわりに

今回は記事はTypeORM + SQLite(expo-sqlite)のテストが書けなかった話でした。 ホントは出来たことを公開したかったのですが…。

出来ていないことを外部に公開することを躊躇いましたが、弊社のValueに全開オープンがあるので大丈夫なはず!

note.com

最後に弊社ではエンジニアを募集しております。 興味がある、話を聞いてみたい、応募したいという方はお気軽にご応募ください!

open.talentio.com