应使用 codeception 的 functional 模块而非 unit 模块测试 yii 控制器,因后者不启动应用实例导致 $this->action 和 yii::$app 为 null;functional 模块完整启动 yii 应用但不依赖 http 服务器,需配置 tests/functional.suite.yml 启用 yii2 模块且 part 包含 orm 或 email,测试类须继承 codeception\test\unit,方法名以 test 开头并提示 functionaltester 类型;get 用 amonpage,post 用 submitform(自动处理 csrf),json 请求用 sendajaxpostrequest 并手动设 content-type 及 csrf header;重定向用 seecurrenturlequals,渲染内容用 see 或 seeresponsecontainstext,状态码用 seeresponsecodeis;fixture 需在测试方法内调用 havefixtures,tablename() 返回无前缀表名如 'user',手动修改数据库后须在 _after() 中 cleandatabase。

别用 Codeception 的 unit 模块测 Yii 控制器——$this->action 和 Yii::$app 全是 null,测试根本跑不起来。
控制器测试必须走 functional 模块
Yii 控制器严重依赖应用上下文(路由、组件、session、request 等),unit 模块不启动应用实例,所有框架级调用都会失败。functional 模块会完整启动 Yii 应用(类似真实请求),但又不走 HTTP 服务器,速度快、可控性强。
- 确保
tests/functional.suite.yml启用了Yii2模块,且part至少包含orm或email(哪怕没用到,缺了会报错) - 测试类必须继承
Codeception\Test\Unit,不能继承PHPUnit\Framework\TestCase—— 否则$I不可用 - 方法名必须以
test开头,且参数类型提示为FunctionalTester
GET/POST/JSON 请求怎么发才对
直接调用 actionIndex() 是错的:它绕过中间件、过滤器、绑定逻辑,结果不可信;用 HTTP 方式触发才能覆盖真实路径。
- GET 请求带参数:用
$I->amOnPage('/site/index?id=123'),不要拼数组传参 - 普通表单 POST:用
$I->submitForm('#login-form', ['username' => 'admin']),它自动提取并提交 CSRF 隐藏字段 - JSON POST 请求:必须用
$I->sendAjaxPostRequest('/api/v1/user', ['name' => 'test']),并手动加 header:$I->haveHttpHeader('Content-Type', 'application/json') - CSRF 必须处理:若测试环境未关闭
enableCsrfValidation,sendAjaxPostRequest()不会自动携带 token,得提前$I->grabCSRF()并手动设 header
重定向和响应断言别用错方法
Yii 控制器常用 $this->redirect() 或 return $this->render(),响应类型不同,断言方式也不同。
- 重定向后检查 URL:用
$I->seeCurrentUrlEquals('/site/success'),不是$I->seeInCurrentUrl('success')(后者是子串匹配,易误判) - 渲染页面后检查内容:用
$I->see('Welcome')或$I->seeResponseContainsText('Welcome'),别用$I->canSeeResponseContainsJson()(那是给 API 响应用的) - 检查状态码:统一用
$I->seeResponseCodeIs(200),不要写$I->assertEquals(200, $I->getStatusCode())—— 功能性断言更稳定
fixture 和数据库清理容易被忽略
functional 测试默认不自动清库,上一个测试插入的数据会影响下一个;fixture 虽好,但加载位置和时机不对就会失效。
- 在测试方法开头调用
$this->tester->haveFixtures([...]),不是在_before()里——否则 fixture 加载时机早于应用初始化,Yii::$app还没就位 - fixture 类必须定义
tableName()返回真实表名(如'user'),不能返回带前缀的{{%user}},否则数据插不进库 - 如果测试中手动执行了
INSERT或UPDATE,记得在_after()里用$I->cleanDatabase()或显式TRUNCATE,否则污染后续测试
最常漏的是 CSRF 处理和 fixture 表名写法——一个导致 400 错误,一个导致“查不到数据”却死活看不出哪错了。











