如何在 SQL INSERT 语句上使用 PHPUnit 获取模拟和 PDO

How to get a mock & PDO working with PHPUnit on a SQL INSERT statement

我有一个名为 ip_requestTest.php 的测试 class,它具有以下功能:

public function testSetTimestampCount()
    {

        $validData = [
            'id' => '99',
            'ip' => '127.0.0.1',
            'timestamp' => '2021-06-03 10:28:43.000'
        ];
        $table = 'ip';

        $stmt = $this->createMock(\PDOStatement::class);
        $stmt->expects($this->once())
            ->method('execute')
            ->with($validData)
            ->willReturn(true);

        global $pdo;
        $pdo = $this->createMock('PDO');
        $pdo->expects($this->once())
            ->method('prepare')
            ->with("INSERT INTO {$table} id,address,timestamp VALUES (:ip, :id, :timestamp)")
            ->willReturn($stmt);

        $ipStmt = new ip_request();
        $ipStmt->setTimestampCount($validData);

    }

现在最后一行是问题所在,$ipStmt->setTimestampCount($validData);

我的实际 setTimestampCount() 函数不只接受一个参数。

看起来像这样:

    function setTimestampCount($ip,$id,$conn)
    {

        $query = $conn->prepare ("INSERT INTO `ip` (`id`, `address` ,`timestamp`)VALUES (:id,:ip,CURRENT_TIMESTAMP)");
        $query ->bindValue(':id', $id, PDO::PARAM_INT);
        $query ->bindValue(':ip', $ip, PDO::PARAM_STR);
        $query->execute();

    }

任何人都可以帮助我,或者给我任何关于如何正确测试此功能的线索吗?

如何将数据包含到测试函数调用中?

谢谢

更新 1:

尝试了 splat 运算符的建议,还删除了时间戳插入,因为这可以通过插入数据自动完成。

还是不行,我得到的错误是 Too few arguments to function 2 passed, exactly 3 expected. 我显然知道这是什么意思,但是 splat 运算符如何处理这个问题?如果有的话?

您的方法 setTimestampCount($ip,$id,$conn) 需要这 3 个参数。

你传入的3是$validData的内容,即

'id' => '99',
'ip' => '127.0.0.1',
'timestamp' => '2021-06-03 10:28:43.000'

相反,您需要从该数组中删除时间戳参数,并将其替换为您的模拟连接。

其次,您正在这样做:

$stmt->expects($this->once())
        ->method('execute')
        ->with($validData)
        ->willReturn(true);

但是 execute 方法没有参数:

$query->execute();

它也没有 return 任何东西(或者至少你没有将它 return 的任何东西分配给一个变量),所以没有必要做 ->willReturn(true);

所以上面应该只是:

$stmt->expects($this->once())
        ->method('execute');

测试时间戳也没有意义。您应该做的就是检查您传递给 prepare 方法的字符串。所以你需要检查两个字符串看起来是否相同。目前您正在测试的字符串是:

"INSERT INTO `ip` (`id`, `address` ,`timestamp`)VALUES (:id,:ip,CURRENT_TIMESTAMP)"

并且您指定

"INSERT INTO {$table} id,address,timestamp VALUES (:ip, :id, :timestamp)

所以诸如缺少 ()、参数之间的间距差异等都会导致此测试失败。它们必须相同。

最后,您实际上并没有测试 ipid 或时间戳值是什么。 prepare 语句实际上并没有使用它们;它只有字符串 :id,:ip,CURRENT_TIMESTAMP.

您还应该真正测试 bindValue 方法,它确实使用了 ip 和 id 值。由于您要调用它两次,这会稍微复杂一些,但是您可以使用 PHPUnit's withConsecutive method 来处理这个问题:

$stmt->expects($this->exactly(2))
     ->method('bindValue')
     ->withConsecutive(
         [':id', $id, PDO::PARAM_INT],
         [':ip', $ip, PDO::PARAM_STR]
     );

所以我认为该方法应该如下所示:

public function testSetTimestampCount()
{
    $table = 'ip';
    $id = '99';
    $ip => '127.0.0.1';
    
    $stmt = $this->createMock(\PDOStatement::class);
    $stmt->expects($this->once())
        ->method('execute');
        
    $stmt->expects($this->exactly(2))
        ->method('bindValue')
        ->withConsecutive(
             [':id', $id, PDO::PARAM_INT],
             [':ip', $ip, PDO::PARAM_STR]
        );
        
    $pdo = $this->createMock('PDO');
    $pdo->expects($this->once())
        ->method('prepare')
        ->with("INSERT INTO `ip` (`id`, `address` ,`timestamp`)VALUES (:id,:ip,CURRENT_TIMESTAMP)")
        ->willReturn($stmt);
        
    $validData = [
        'id' => $id,
        'ip' => $ip,
        'conn' => $pdo
    ];

    $ipStmt = new ip_request();
    $ipStmt->setTimestampCount(...$validData);
}