编程

Laravel + Cypress 集成

1162 2022-02-17 19:46:53

本扩展包提供了必要的模板文件,用于使用 Cypress 快速开始测试 Laravel 应用。

安装

如果你还未安装Cypress, 第一步便是如下:

npm install cypress --save-dev && npx cypress open

初始化安装时,Cypress 会在项目根目录下创建一个 ./cypress 目录, 以及 cypress.json 配置文件。

现在你可以通过 composer 安装相关依赖,以 development-only 的方式拉取相关依赖。

composer require laracasts/cypress --dev

最后运行 cypress:boilerplate 命令,为 Cypress 测试复制初始化模板文件。

php artisan cypress:boilerplate

好了,现在可以开始了。我们已经在项目 cypress.json 文件中声明了一些初始设置。查看该文件确保一切如期设置。特别注意,将 baseUrl 属性设置正确(默认为应用的 `APP_URL 环境设置)。

环境处理

在运行 php artisan cypress:boilerplate 命令后,你的根目录下会添加一个 .env.cypress 文件。此文件是 .env 文件的副本。请根据实际测试需要更新相关项目。

你可能会使用特定的数据库,使你的 Cypress 验收测试与你的开发环境数据库隔离。

DB_CONNECTION=mysql
DB_DATABASE=cypress

当运行 Cypress 测试时,此包会自动备份你的主 .env 文件,切换到 .env.cypressw 文件。等到测试完成,环境文件会被重置成原来的文件。

All Cypress tests run according to the environment specified in .env.cypress.

然而,当 Cypress 测试失败时,手动浏览导致测试失败的应用状态通常是有用的手段。如果你的环境在每次测试运行后都自动恢复,你无法手动浏览。解决办法是,暂时禁用 Cypress 重置环境的任务。在 cypress/support/index.js 中注释掉如下部分:

after(() => {
  // cy.task("activateLocalEnvFile", {}, { log: false });
});

这样就可以了!

API

这个扩展包会将一系列命令添加到 Cypress 工作流中,使之更加熟悉 Laravel 测试环境。

我们通过在应用中暴露一些 Cypress 特定的端口(endpoints)实现。不用担心,这些接口在生产环境中不会生效。

cy.login()

找一个已存在的用户匹配可选属性,将其设置会测试的授权用户。如果用户不存在,则新建用户然后登录。

test('authenticated users can see the dashboard', () => {
  cy.login({ username: 'JohnDoe' });

  cy.visit('/dashboard').contains('Welcome Back, JohnDoe!');
});

你需要在积极加载用户模型的关联模型或者在服务器返回前指定特定的模型工厂状态,在 cy.login() 中传入一个对象如下:

test('authenticated users can see the dashboard', () => {
    cy.login({
        attributes: { username: 'JohnDoe' },
        state: ['guest'],
        load: ['profile']
    });

    cy.visit('/dashboard').contains('Welcome Back, JohnDoe!');
});

如果用PHP编写,这个对象会被翻译成如下:

$user = User::factory()->guest()->create([ 'username' => 'JohnDoe' ])->load('profile');

auth()->login($user);

cy.currentUser()

从服务器中获取当前授权用户。相当于 Laravel 的 auth()->user()

test('assert the current user has email', () => {
    cy.login({ email: 'joe@example.com' });

    cy.currentUser().its('email').should('eq', 'joe@example.com');
    
    // or...
    
    cy.currentUser().then(user => {
        expect(user.email).to.eql('joe@example.com');
    });
});

cy.logout()

退出当前登录用户。相当于 Laravel 的 auth()->logout()

test('once a user logs out they cannot see the dashboard', () => {
  cy.login({ username: 'JohnDoe' });

  cy.logout();

  cy.visit('/dashboard').assertRedirect('/login');
});

cy.create()

使用 Laravel 工厂创建和持久化一个新的 Eloquent 记录。 

test('it shows blog posts', () => {
  cy.create('App\\Post', { title: 'My First Post' });

  cy.visit('/posts').contains('My First Post');
});

如上 cy.create() 相当于:

App\Post::factory()->create(['title' => 'My First Post']);

你也可以在第二个参数中指定记录数量。这样将会返回一个 post 集合。

test('it shows blog posts', () => {
  cy.create('App\\Post', 3, { title: 'My First Post' });
});

最后,你也可以在 cy.create() 中传入一个对象。如果你需要积极加载(eager load)关联模型或者在给定的模型工厂创建模型记录的话,这样做可能是更好的选择。

test('it shows blog posts', () => {
    cy.create({
        model: 'App\\Post',
        attributes: { title: 'My First Post' },
        state: ['archived'],
        load: ['author'],
        count: 10
    })
});

此举相当于 PHP 的代码:

$user = \App\Post::factory(10)->archived()->create([ 'title' => 'My First Post' ])->load('author');

auth()->login($user);

cy.refreshRoutes()

在 Cypress 测试套件运行之前,这个会自动获取 Laravel 应用的所有路由集合,将其存放在内存中。你无需手动调用此方法。虽然如此,如果你的路由会因为特别测试的副作用而产生改变,你也可以调用:

test('it refreshes the list of Laravel named routes in memory', () => {
    cy.refreshRoutes();
});

cy.refreshDatabase()

在测试数据库中触发 migrate:refresh。通常,在调用 beforeEach  时运行,确保每次新测试,你的数据库都是全新和干净的。

beforeEach(() => {
  cy.refreshDatabase();
});

test('it does something', () => {
  // php artisan migrate:fresh has been
  // called at this point.
});

cy.seed()

在当前 Cypress 环境中,运行所有的数据库数据填充,或者运行单个数据填充类。

test('it seeds the db', () => {
  cy.seed('PlansTableSeeder');
});

假设 .env.cypress 文件的 APP_ENV 设为 ”acceptance“,如上调用相当于:

php artisan db:seed --class=PlansTableSeeder --env=acceptance

cy.artisan()

在当前 Cypress 测试环境中触发 Artisan 命令。记住在选项前使用双横线:

test('it can create posts through the command line', () => {
  cy.artisan('post:make', {
    '--title': 'My First Post',
  });

  cy.visit('/posts').contains('My First Post');
});

这个调用相当于:

php artisan post:make --title="My First Post"

cy.php()

此命令将允许你触发和随意评估 PHP 代码:

test('it can evaluate PHP', () => {
    cy.php(`
        App\\Plan::first();
    `).then(plan => {
        expect(plan.name).to.equal('Monthly'); 
    });
});

请慎重使用此命令。当验证应用或者数据库状态时,它或许挺有用的。同样可以用于设置测试的”世界“,尽管如此,使用 cy.seed() 对目标数据填充通常是更好的方式。

Routing路由

每次允许测试套件时,这个包会获取 Laravel 应用中的所有具名的路由,将其存放于内存中。你另外会发现一个 ./cypress/support/routes.json 文件,包含一堆这样的 JSON。此包重写了基础的 cy.visit() 方法,使之允许传路由名作为参数作为可选方式替代原来的 URL。

test('it loads the about page using a named route', () => {
    cy.visit({
        route: 'about'
    });
});

如果路由名称中需要通配符,你可以使用 parmeters 属性包含:

test('it loads the team dashboard page using a named route', () => {
    cy.visit({
        route: 'team.dashboard',
        parameters: { team: 1 }
    });
});

如果你要获取应用的全部路由清单,使用 Cypress.Laravel.routes 属性。

// Get an array of all routes for your app. 
Cypress.Laravel.routes; // ['home' => []]

另外,如果你要将路由名称转换成它关联的 URL,可以这样使用 Cypress.Laravel.route() 方法:

Cypress.Laravel.route('about'); // /about-page

Cypress.Laravel.route('team.dashboard', { team: 1 }); // /teams/1/dashboard

Security