こんにちは、株式会社カミナシのエンジニア @imuです。
はじめに
みなさんのプロダクトでテスト書いてますか?
『カミナシ』はオフライン機能を提供しており、ローカルデータベースは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
しばらくするとプロジェクトが作成されます!
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を利用します。
yarn add github:cuibonobo/typeorm-package reflect-metadata --dev
tsconfig.json で以下の設定を有効にします。
"emitDecoratorMetadata": true "experimentalDecorators": true
テストフレームワークのインストール
Expoガイドに載っているJestを利用します。
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
データベースを利用します。
別のデータベースを利用する場合はtype
をmysql
, 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-sqlite
の SQLiteDatabase.exec
でエラーになっていました。
thenで正常に終わっているが、nativeResultSetsに入っていないようです。
ここから数時間近くハマって解決策が見つかっていません...。
確認したことは
- 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に全開オープンがあるので大丈夫なはず!
最後に弊社ではエンジニアを募集しております。 興味がある、話を聞いてみたい、応募したいという方はお気軽にご応募ください!