3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Sencha Architect と Sencha Test の開発環境に REST API モックサーバーを追加する

Last updated at Posted at 2020-01-31

Sencha Architect と Sencha Test の開発環境に REST API モックサーバーを追加する手順をメモっとく。

この記事では、Ext.ux.ajax.SimManagerExt.ux.ajax.Simlet を用いて REST API のモックサーバーを Sencha Architect と Sencha Test の開発環境に追加する。そうすることでコンポーネントごとにサーバーとの通信が発生するテストを書けるようにしている。

Sencha Architect と Sencha Test で開発環境を構築するの続き。

前提条件

この記事では下記バージョンの Sencha ツール群を使用している。

Tool Version
Sencha Architect 4.2.7
Sencha Ext JS 7.1.0.48
Sencha Cmd 7.1.0.16
Sencha Test 2.3.0.328

REST API モックサーバーを追加する

app.json にある依存パッケージの設定に "ux" を追加する。Ext.ux.ajax.SimManagerExt.ux.ajax.Simlet を使用するために必要な設定になる。

$ sed -i "" 's|"font-awesome"$|"font-awesome", "ux"|' test/app.json

RestSimlet.js と Server.js を app ディレクトリに追加する。

test/app/RestSimlet.js
Ext.define('RestSimlet', {
    extend: 'Ext.ux.ajax.JsonSimlet',
    alias: 'simlet.rest',

    doGet: function (ctx) {
        ctx.matches = this.getMatches(ctx);
        return this.callParent(arguments);
    },

    doPut: function (ctx) {
        return this.doGet(ctx);
    },

    doDelete: function (ctx) {
        return this.doGet(ctx);
    },

    privates: {
        getMatches: function (ctx) {
            var url = ctx.url.split(/\?|#/)[0],
                pattern = ctx.xhr.simlet.url;
            return url.match(pattern);
        }
    }
});
test/app/Server.js
Ext.define('Server', {
    requires: [
        'RestSimlet'
    ],

    singleton: true,

    start: resources => {
        Ext.ux.ajax.SimManager.init({
            defaultType: 'rest',
            delay: 0
        }).register([{
            url: RegExp('^/(\\w+)(/(\\d+))?$'),
            data: ctx => {
                var method = ctx.method.toUpperCase(),
                    resource = ctx.matches[1],
                    id = Number(ctx.matches[3]);

                resources = resources || {};
                resources[resource] = resources[resource] || [];

                var records = resources[resource];

                switch (method) {
                    case 'GET':
                        if (id) {
                            var index = records.findIndex(record => record.id === id);
                            if (index < 0) {
                                this.status = 404;
                                this.statusText = 'Not Found';
                                return;
                            } else {
                                this.status = 200;
                                this.statusText = 'OK';
                                return records[index];
                            }
                        } else {
                            this.status = 200;
                            this.statusText = 'OK';
                            return records;
                        }

                    case 'POST':
                        if (id) {
                            this.status = 405;
                            this.statusText = 'Method Not Allowed';
                            return;
                        } else {
                            var record = Ext.decode(ctx.xhr.body);
                            if (record) {
                                record.id = records.reduce((maximum, record) => Math.max(maximum, record.id), 0) + 1;
                                records.push(record);

                                this.status = 201;
                                this.statusText = 'Created';
                                return record;
                            } else {
                                this.status = 400;
                                this.statusText = 'Bad Request';
                                return;
                            }
                        }

                    case 'PUT':
                        if (id) {
                            var record = Ext.decode(ctx.xhr.body);
                            if (record) {
                                record.id = id;

                                var index = records.findIndex(record => record.id === id);
                                if (index < 0) {
                                    records.push(record);

                                    this.status = 201;
                                    this.statusText = 'Created';
                                    return record;
                                } else {
                                    records[index] = record;

                                    this.status = 200;
                                    this.statusText = 'OK';
                                    return record;
                                }
                            } else {
                                this.status = 400;
                                this.statusText = 'Bad Request';
                                return;
                            }
                        } else {
                            this.status = 405;
                            this.statusText = 'Method Not Allowed';
                            return;
                        }

                    case 'DELETE':
                        if (id) {
                            var index = records.findIndex(record => record.id === id);
                            if (index < 0) {
                                this.status = 404;
                                this.statusText = 'Not Found';
                                return;
                            } else {
                                records.splice(index, 1);

                                this.status = 204;
                                this.statusText = 'No Content';
                                return;
                            }
                        } else {
                            this.status = 405;
                            this.statusText = 'Method Not Allowed';
                            return;
                        }
                }
            }
        }]);
    },

    stop: () => Ext.ux.ajax.SimManager.simlets = []
});

app.js に依存クラスとして Server を追加する。

test/app.js
/*
 * This call registers your application to be launched when the browser is ready.
 */
Ext.application({
    requires: [
        'Ext.Panel',
        'Server'
    ],

    name: 'ExampleTest',

    launch: () => {
        var config = { floated: true, right: 0, bottom: 0 };
        if (Ext.isChrome) Ext.merge(config, { iconCls: 'x-fab fa-chrome',  title: 'Chrome'  });
        if (Ext.isEdge  ) Ext.merge(config, { iconCls: 'x-fab fa-edge',    title: 'Edge'    });
        if (Ext.isGecko ) Ext.merge(config, { iconCls: 'x-fab fa-firefox', title: 'Firefox' });
        if (Ext.isOpera ) Ext.merge(config, { iconCls: 'x-fab fa-opera',   title: 'Opera'   });
        if (Ext.isSafari) Ext.merge(config, { iconCls: 'x-fab fa-safari',  title: 'Safari'  });
        Ext.create('Ext.Panel', config).show();
    }
});
$ git add .
$ git commit -m "Add REST API mock server"

REST API モックサーバーの動作を確認する

Sencha Architect を開く。

User モデルを作成する。

  • userClassName: User
  • fields:
    • id
    • name
  • proxy:
    • type: rest
    • url: /users

User model

Users ストアを作成する。

  • userClassName: Users
  • storeId: Users
  • autoLoad: true
  • model: Example.model.User
  • data:
[
    { id: 1, name: "Alice"   },
    { id: 2, name: "Bob"     },
    { id: 3, name: "Charlie" },
    { id: 4, name: "Dave"    }
]

Users store

Users グリッドを作成する。デザインビューにレコードが表示されていれば動作している。

  • userClassName: Books
  • store: Users
  • columns:
    • Column
      • xtype: gridcolumn
      • flex: auto
      • width: (none)
      • dataIndex: id
      • text: id
    • Column
      • xtype: gridcolumn
      • flex: 1
      • width: (none)
      • dataIndex: name
      • text: name

Code view of Users grid

Design view of Users grid

Sencha Architect のプロジェクトを保存する。そして、プレビューをクリックし、下記のように表示されていれば実装がきちんとできている。

Users grid in main application

Sencha Studio で Jasmine テストスイートを UsersTest という名前で作成する。

Create Jasmine test suite

Users グリッドに自動ロードで5件のユーザーが読み込まれることを確認するテストを記述する。

ここで beforeEach メソッドで、REST API モックサーバーを開始させ、テスト対象の Users ストアと Users コンポーネントを生成し、テストアプリケーションのビューポートにコンポーネントを追加している。テストではそのグリッドを取得し、グリッドが参照するストアのレコード数が5であることを検証している。afterEach メソッドでテストのために生成したストアとコンポーネントを破棄し、REST API モックサーバーを停止させ、ひとつのテストケースが完了する。

test/test/component/UsersTest.js
describe("UsersTest", function () {
    let store;
    let component;

    beforeEach(function () {
        Server.start({
            users: [
                { id: 1, name: "Alice"   },
                { id: 2, name: "Bob"     },
                { id: 3, name: "Charlie" },
                { id: 4, name: "Dave"    },
                { id: 5, name: "Eve"     }
            ]
        });
        store = Ext.create("Example.store.Users");
        component = Ext.create("Example.view.Users");
        Ext.getApplication().viewport.add(component);
    });

    afterEach(function () {
        component.destroy();
        store.destroy();
        Server.stop();
    });

    it("loads 5 records", function () {
        ST.grid("users")
            .and(grid => {
                var users = grid.getStore();
                expect(users.count()).toBe(5);
            });
    });
});

このテストが無事に完了すれば開発環境に REST API モックサーバーを追加できたことになる。


この記事では Sencha Architect と Sencha Test の開発環境に REST API モックサーバーを追加する方法について説明した。SPA のコンポーネントテストも高速に実現できる環境なので、興味のある人はトライアル版で試してみてほしい。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?