如何使用PHPUnit测试LoadEnvironmentVariables类?
嘿,刚接触PHPUnit就能想到要覆盖这几个关键场景,很棒!针对你提到的三个测试需求,我们可以通过**模拟依赖对象(也就是Core类的实例)**来绕过外部模块的限制,精准测试每个代码分支。下面是具体的实现思路和代码示例:
1. 测试变量是否已加载
要验证这个场景,我们需要让$app->configurationIsCached()返回false,触发DotEnv的加载逻辑,然后检查环境变量是否被正确设置。
2. 测试配置已缓存时不执行加载操作
这里需要让$app->configurationIsCached()返回true,然后验证load()方法完全没有被调用过——这时候我们可以用PHPUnit的**Spy(带调用跟踪的Mock)**来监控方法调用情况。
3. 测试缺少必填变量时是否抛出异常
同样让$app->configurationIsCached()返回false,但提供一个缺少必填变量的环境配置,然后断言代码会抛出DotEnv的ValidationException。
完整测试类示例
<?php declare(strict_types=1); namespace Tests\Foundation\Bootstrap; use Foundation\Bootstrap\LoadEnvironmentVariables; use Foundation\Core; use PHPUnit\Framework\TestCase; use Dotenv\Exception\ValidationException; class LoadEnvironmentVariablesTest extends TestCase { /** * 每个测试前清理环境变量,避免测试间互相干扰 */ protected function setUp(): void { parent::setUp(); $requiredVars = ['APP_URL', 'DB_NAME', 'DB_USER', 'DB_PASS', 'DB_HOST']; foreach ($requiredVars as $var) { putenv($var); unset($_ENV[$var], $_SERVER[$var]); } } /** * 测试配置未缓存时,环境变量被正确加载 */ public function testEnvironmentVariablesAreLoadedWhenConfigIsNotCached() { // 模拟Core实例:配置未缓存,返回测试用.env文件的路径 $appMock = $this->createMock(Core::class); $appMock->method('configurationIsCached')->willReturn(false); // 假设你的测试用.env放在tests/fixtures目录下 $appMock->method('get')->with('paths.base')->willReturn(__DIR__ . '/../../fixtures'); // 实例化测试类,触发加载逻辑 new LoadEnvironmentVariables($appMock); // 断言所有必填变量都已加载 $this->assertEquals('http://test.local', getenv('APP_URL')); $this->assertEquals('test_db', getenv('DB_NAME')); $this->assertNotNull(getenv('DB_USER')); $this->assertNotNull(getenv('DB_PASS')); $this->assertNotNull(getenv('DB_HOST')); } /** * 测试配置已缓存时,load方法不会被调用 */ public function testLoadMethodIsSkippedWhenConfigIsCached() { // 创建LoadEnvironmentVariables的Spy,用来跟踪load方法的调用 $loadEnvSpy = $this->getMockBuilder(LoadEnvironmentVariables::class) ->setConstructorArgs([$this->createMock(Core::class)]) ->onlyMethods(['load']) ->getMock(); // 模拟Core实例:配置已缓存 $appMock = $this->createMock(Core::class); $appMock->method('configurationIsCached')->willReturn(true); // 重新调用构造函数(因为Spy已经实例化过一次,这里传入配置好的appMock) $loadEnvSpy->__construct($appMock); // 断言load方法从未被调用 $loadEnvSpy->expects($this->never())->method('load'); } /** * 测试缺少必填变量时抛出异常 */ public function testThrowsExceptionWhenRequiredVariablesAreMissing() { // 模拟Core实例:配置未缓存,返回缺少变量的.env目录路径 $appMock = $this->createMock(Core::class); $appMock->method('configurationIsCached')->willReturn(false); // tests/fixtures/missing-env目录下的.env故意缺少DB_PASS $appMock->method('get')->with('paths.base')->willReturn(__DIR__ . '/../../fixtures/missing-env'); // 断言会抛出DotEnv的验证异常 $this->expectException(ValidationException::class); $this->expectExceptionMessage('DB_PASS is required'); // 实例化测试类,触发异常 new LoadEnvironmentVariables($appMock); } }
几个关键注意事项
- 测试用.env文件:你需要在
tests/fixtures下创建两个.env文件:- 一个包含所有必填变量的正常.env
- 一个故意缺少某个变量(比如DB_PASS)的.env,放在
missing-env子目录下
- 环境变量隔离:
setUp方法里的清理操作很重要,确保每个测试都从干净的环境开始,避免之前的测试残留影响结果 - Mock的灵活使用:如果不想依赖真实文件,可以用DotEnv的
createFromString方法直接在内存中构建环境变量字符串,这样测试会更轻量,比如:// 在testEnvironmentVariablesAreLoadedWhenConfigIsNotCached里替换load方法的实现 $loadEnvMock = $this->getMockBuilder(LoadEnvironmentVariables::class) ->setConstructorArgs([$appMock]) ->onlyMethods(['load']) ->getMock(); $loadEnvMock->method('load')->willReturnCallback(function () { $dotenv = \Dotenv\Dotenv::createFromString( "APP_URL=http://test.local\nDB_NAME=test_db\nDB_USER=root\nDB_PASS=secret\nDB_HOST=127.0.0.1" ); $dotenv->safeLoad(); $dotenv->required(['APP_URL', 'DB_NAME', 'DB_USER', 'DB_PASS', 'DB_HOST']); });
内容的提问来源于stack exchange,提问作者KeironLowe
相关产品推荐
相关产品推荐

