カテゴリー: BackEnd

Laravelのブラウザテスト「Dusk」で非同期で重たい処理のテストを実装してみよう

はじめに

前回の続きで、今回はLaravel Duskを使用して、非同期なテストを実装したいと思います。以前調査した際に、重い処理がある場合には、待機する必要があることが分かったので、Laravel Duskで非同期で重い処理を待機する方法を紹介します。

JavaScriptの式で待機する

Ajax通信を行った後の画面を検証したり、操作したりしたい場合は、JavaScriptの式を実行し、結果がtrueになるまで待機することができます。

テスト対象となるコード

index.blade.php

<div id="result"></div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script>
    var books = [];
    $.ajax({
        url: 'http://localhost/api/books',
        success: function(response) {
            books = response;
            console.log(books);

            var bookList = '<table>' +
                '    <tr>' +
                '        <th>ID</th>' +
                '        <th>タイトル</th>' +
                '        <th>著者</th>' +
                '        <th colspan="2"></th>' +
                '    </tr>';
            books.forEach(book => {
                bookList +=
                    '<tr>' +
                        '<td>' + book.id + '</td>' +
                        '<td>' + book.title + '</td>' +
                        '<td>' + book.author + '</td>' +
                        '<td><a href="/books/' + book.id + '">詳細</a></td>' +
                    '</tr>';
            });
            bookList += '</table>';
            $('#result').html(bookList);
        }
    });
</script>

Ajax通信では次のJSONがレスポンスされます。

[
    {
        "id": 3,
        "title": "タイトルテスト",
        "author": "著者テスト",
        "is_check_out": false,
        "created_at": "2020-10-23T22:50:47.000000Z",
        "updated_at": "2020-10-24T01:26:17.000000Z"
    }
]

Duskのテストコード

今回は、ページが読み込まれて、書籍の一覧表示をテストします。

class BookTest extends DuskTestCase
{

    public function testIndex()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/books')
                ->waitUntil('books.length > 0')
                ->assertSee('タイトルテスト');
        });
    }
}

waitUntil()メソッドで、JavaScriptの式を渡し、その結果がtrueになると、後続が実行されます。
waitUntil('books.length > 0', 10)というように、第二引数を設定すると、10秒でタイムアウトするようになります。

では、この式がどのような仕組みで評価されているか理解するために、 waitUntil()メソッドのコードを読んでいきたいと思います。

    /**
     * Wait until the given script returns true.
     *
     * @param  string  $script
     * @param  int  $seconds
     * @param  string  $message
     * @return $this
     *
     * @throws \Facebook\WebDriver\Exception\TimeOutException
     */    public function waitUntil($script, $seconds = null, $message = null)
    {
        if (! Str::startsWith($script, 'return ')) {
            $script = 'return '.$script;
        }

        if (! Str::endsWith($script, ';')) {
            $script = $script.';';
        }

        return $this->waitUsing($seconds, 100, function () use ($script) {
            return $this->driver->executeScript($script);
        }, $message);
    }

まず、コードの整形が行われ、式に returnがない場合は、先頭に着けます。セミコロンも同様に付けます。
その後、 waitUsing()メソッドを使って、100msごとに、WebDriverにJavaScriptを実行させます。
waitUsing()メソッドは、クロージャの中で trueが返却されるか、タイムアウトすると終了します。
ちなみに、タイムアウトまでの秒数が指定されていない場合は、 waitUsing()メソッドのデフォルト値である5秒でタイムアウトします。

DOM要素の表示を待つ

DOMの表示を待ち、その結果を検証することもできます。

テスト対象となるコード

先ほどのbladeファイルを変更します。

<div id="result"></div>

<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>

<script>
    var books = [];

    function index() {
        $.ajax({
            url: '/api/books',
            success: function(response) {
                books = response;
                console.log(books);

                var bookList = '<table>' +
                    '    <tr>' +
                    '        <th>ID</th>' +
                    '        <th>タイトル</th>' +
                    '        <th>著者</th>' +
                    '        <th colspan="2"></th>' +
                    '    </tr>';
                books.forEach(book => {

                    bookList +=
                        '<tr>' +
                        '<td>' + book.id + '</td>' +
                        '<td>' + book.title + '</td>' +
                        '<td>' + book.author + '</td>' +
                        '<td><a href="/books/' + book.id + '">詳細</a></td>';

                    if (book.is_check_out) {
                        bookList += '<td><button type="button" class="return" data-id="'+book.id+'">返却する</button></td>';
                    } else {
                        bookList += '<td><button type="button" class="checkout" data-id="'+book.id+'">借りる</button></td>'
                    }

                    bookList += '</td>';
                });
                bookList += '</table>';
                $('#result').html(bookList);
            }
        });
    }

    // 借りるボタンをクリック
    $(document).on('click', '.checkout', function() {
        const id = $(this).data('id');
        $.ajax({
            url: '/api/books/' + id + '/check_out',
            type: 'POST',
            success: function(response) {
                index();
            }
        });
    });

    // 返却ボタンをクリック
    $(document).on('click', '.return', function() {
        const id = $(this).data('id');
        $.ajax({
            url: '/api/books/' + id + '/return',
            type: 'POST',
            success: function(response) {
                index();
            }
        });
    });

    index();
</script>

本を借りたり、返却したりすることができるようになりました。この借りる・返却するボタンのテストを実装したいと思います。

Duskテストコードの実装

class BookTest extends DuskTestCase
{
    public function testCheckOutBook()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/books')
                ->waitUntil('books.length > 0')
                ->click('.checkout')
                ->waitForText('返却する')
                ->assertSee('返却する');
        });
    }

    public function testReturnBook()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/books')
                ->waitUntil('books.length > 0')
                ->click('.return')
                ->waitForText('借りる')
                ->assertSee('借りる');
        });
    }

まず、借りる/返却するボタンが表示されるのを待つため、一覧表示のテストと同じように待機します。
ボタン動作の検証ですが、今回は、ボタンの文言が借りた後は「返却する」、返却した後は「借りる」に変わっていることを検証するため、 waitForTextを使用して待機させています。
waitForText('返却する', 10)とすると、10秒でタイムアウトになります。
この他にも、 waitFor('.return')のように、CSSセレクタを使用して待機させることも可能です。

waitForTextのコードも見てみましょう。

    /**
     * Wait for the given text to be visible.
     *
     * @param  array|string  $text
     * @param  int  $seconds
     * @return $this
     *
     * @throws \Facebook\WebDriver\Exception\TimeOutException
     */    public function waitForText($text, $seconds = null)
    {
        $text = Arr::wrap($text);

        $message = $this->formatTimeOutMessage('Waited %s seconds for text', implode("', '", $text));

        return $this->waitUsing($seconds, 100, function () use ($text) {
            return Str::contains($this->resolver->findOrFail('')->getText(), $text);
        }, $message);
    }

先ほどと同じように waitUsing()メソッドを使って、100msおきに Str::conatins()関数により評価され、テキストが存在してtrueが返却されるか、タイムアウトでfalseが返却されます。

さいごに

いかがでしたか。Laravel Duskで、シンプルな非同期処理の待機を実装することができます。ぜひ試してみてください。

おすすめ書籍

カイザー

シェア
執筆者:
カイザー
タグ: laravelphp

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

4週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前