# 测试 CLI 命令

## 使用 MockInputOutput

Added in version 4.5.0.

### MockInputOutput

MockInputOutput 提供了一种便捷的方式来测试需要用户输入的命令，例如 `CLI::prompt()`、`CLI::wait()` 和 `CLI::input()`。

测试执行期间，可用 `MockInputOutput` 替换 `InputOutput` 类，从而捕获输入和输出。

备注

使用 `MockInputOutput` 时，无需再使用 StreamFilterTrait、CITestStreamFilter 和 PhpStreamWrapper。

#### 辅助方法

##### getOutput(?int $index = null): string

获取输出内容。

- 调用 `$io->getOutput()` 时，返回完整输出字符串。

- 指定 `0` 或正数时，返回输出数组中对应索引的项。 每项是单次 `CLI::fwrite()` 调用的输出。

- 指定负数 `-n` 时，返回输出数组倒数第 `n` 项。

##### getOutputs(): array

返回输出数组。每项是单次 `CLI::fwrite()` 调用的输出。

### 使用方法

- `CLI::setInputOutput()` 可将 `MockInputOutput` 实例设置到 `CLI` 类。

- `CLI::resetInputOutput()` 重置 `CLI` 类中的 `InputOutput` 实例。

- `MockInputOutput::setInputs()` 设置用户输入数组。

- `MockInputOutput::getOutput()` 获取命令输出。

以下测试代码用于测试命令 `spark db:table`：

    <?php

    use CodeIgniter\CLI\CLI;
    use CodeIgniter\Test\CIUnitTestCase;
    use CodeIgniter\Test\DatabaseTestTrait;
    use CodeIgniter\Test\Mock\MockInputOutput;

    final class DbTableTest extends CIUnitTestCase
    {
        use DatabaseTestTrait;

        protected $migrateOnce = true;

        public function testDbTable(): void
        {
            // Set MockInputOutput to CLI.
            $io = new MockInputOutput();
            CLI::setInputOutput($io);

            // User will input "a" (invalid value) and "0".
            $io->setInputs(['a', '0']);

            command('db:table');

            // Get the whole output string.
            $output = $io->getOutput();

            $expected = 'Which table do you want to see? [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]: a';
            $this->assertStringContainsString($expected, $output);

            $expected = 'Data of Table "db_migrations":';
            $this->assertStringContainsString($expected, $output);

            // Remove MockInputOutput.
            CLI::resetInputOutput();
        }
    }

## 不使用 MockInputOutput

### 测试 CLI 输出

#### StreamFilterTrait

Added in version 4.3.0.

StreamFilterTrait 提供了一组替代方法。

有些情况测试起来比较困难。此时捕获流（如 PHP 自身的 STDOUT 或 STDERR）可能会有帮助。`StreamFilterTrait` 可用于捕获指定流的输出。

##### 使用方法

- `StreamFilterTrait::getStreamFilterBuffer()` 获取缓冲区捕获的数据。

- `StreamFilterTrait::resetStreamFilterBuffer()` 重置捕获的数据。

测试用例中的使用示例：

    <?php

    namespace Tests;

    use CodeIgniter\CLI\CLI;
    use CodeIgniter\Test\CIUnitTestCase;
    use CodeIgniter\Test\StreamFilterTrait;

    final class SomeTest extends CIUnitTestCase
    {
        use StreamFilterTrait;

        public function testSomeOutput(): void
        {
            CLI::write('first.');

            $this->assertSame("\nfirst.\n", $this->getStreamFilterBuffer());

            $this->resetStreamFilterBuffer();

            CLI::write('second.');

            $this->assertSame("second.\n", $this->getStreamFilterBuffer());
        }
    }

`StreamFilterTrait` 包含一个会自动调用的配置方法。 详见 测试 Trait。

如果测试中重写了 `setUp()` 或 `tearDown()` 方法，则必须分别调用 `parent::setUp()` 和 `parent::tearDown()` 方法以完成 `StreamFilterTrait` 的配置。

#### CITestStreamFilter

CITestStreamFilter 用于手动/单次场景。

如果只需在单个测试中捕获流，可以不使用 StreamFilterTrait trait，改为手动 为流添加过滤器。

##### 使用方法

- `CITestStreamFilter::registration()` 注册过滤器。

- `CITestStreamFilter::addOutputFilter()` 为输出流添加过滤器。

- `CITestStreamFilter::addErrorFilter()` 为错误流添加过滤器。

- `CITestStreamFilter::removeOutputFilter()` 从输出流移除过滤器。

- `CITestStreamFilter::removeErrorFilter()` 从错误流移除过滤器。

    <?php

    namespace Tests;

    use CodeIgniter\CLI\CLI;
    use CodeIgniter\Test\CIUnitTestCase;
    use CodeIgniter\Test\Filters\CITestStreamFilter;

    final class SomeTest extends CIUnitTestCase
    {
        public function testSomeOutput(): void
        {
            CITestStreamFilter::registration();
            CITestStreamFilter::addOutputFilter();

            CLI::write('first.');

            CITestStreamFilter::removeOutputFilter();
        }
    }

### 测试 CLI 输入

#### PhpStreamWrapper

Added in version 4.3.0.

PhpStreamWrapper 提供了一种方式来测试需要用户输入的方法， 例如 `CLI::prompt()`、`CLI::wait()` 和 `CLI::input()`。

备注

PhpStreamWrapper 是一个流封装类。 如果不了解 PHP 的流封装，请参阅 PHP 手册中的 streamWrapper 类。

##### 使用方法

- `PhpStreamWrapper::register()` 将 `PhpStreamWrapper` 注册到 `php` 协议。

- `PhpStreamWrapper::restore()` 将 php 协议恢复为 PHP 内置的流封装。

- `PhpStreamWrapper::setContent()` 设置输入数据。

重要

PhpStreamWrapper 仅用于测试 `php://stdin`。 但注册后，它将处理所有 php 协议 流， 例如 `php://stdout`、`php://stderr`、`php://memory`。 因此强烈建议仅在需要时注册/取消注册 `PhpStreamWrapper`。 否则，注册期间会干扰其他 PHP 内置流。

测试用例中的使用示例：

    <?php

    namespace Tests;

    use CodeIgniter\CLI\CLI;
    use CodeIgniter\Test\CIUnitTestCase;
    use CodeIgniter\Test\PhpStreamWrapper;

    final class SomeTest extends CIUnitTestCase
    {
        public function testPrompt(): void
        {
            // Register the PhpStreamWrapper.
            PhpStreamWrapper::register();

            // Set the user input to 'red'. It will be provided as `php://stdin` output.
            $expected = 'red';
            PhpStreamWrapper::setContent($expected);

            $output = CLI::prompt('What is your favorite color?');

            $this->assertSame($expected, $output);

            // Restore php protocol wrapper.
            PhpStreamWrapper::restore();
        }
    }