feat: migrate test suite from PHPUnit to Pest

- Replace phpunit/phpunit with pestphp/pest ^3.0
- Drop scrutinizer/ocular (incompatible with Pest's symfony/console)
- Add tests/Pest.php with MockeryPHPUnitIntegration trait binding
- Extract namespace-level function stubs (config, route, dir) to
  tests/Overrides.php, loaded via autoload-dev files
- Convert all 5 test classes to Pest closure-based syntax:
  setUp() → beforeEach(), assertions → expect(), skip() for skipped tests
- Delete tests/TestCase.php (logic moved to Pest.php)
- Stubs (TokenStub, User) unchanged
This commit is contained in:
Joel Male
2026-02-18 10:38:20 +10:00
parent 8ec2c3cc38
commit 97c04ec693
9 changed files with 426 additions and 830 deletions
+118 -253
View File
@@ -1,282 +1,147 @@
<?php
namespace Wearepixel\QuickBooks;
use Mockery;
use Mockery\Mock;
use PHPUnit\Framework\Attributes\Test;
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Exception\ServiceException;
use Wearepixel\QuickBooks\Client;
use Wearepixel\QuickBooks\Token;
/**
* Class ClientTest
*/
class ClientTest extends TestCase
{
/**
* @var Client
*/
protected $client;
beforeEach(function () {
$this->configs = [
'data_service' => [
'auth_mode' => 'oauth2',
'base_url' => 'Development',
'client_id' => 'QUICKBOOKS_CLIENT_ID',
'client_secret' => 'QUICKBOOKS_CLIENT_SECRET',
'scope' => 'com.intuit.quickbooks.accounting',
],
];
/**
* @var array
*/
protected $configs = [];
$this->token_mock = Mockery::mock(Token::class);
$this->client = new Client($this->configs, $this->token_mock);
});
/**
* @var Mock
*/
protected $token_mock;
it('can be constructed', function () {
expect($this->client)->toBeInstanceOf(Client::class);
});
protected function setUp(): void
{
$this->configs = [
'data_service' => [
'auth_mode' => 'oauth2',
'base_url' => 'Development',
'client_id' => 'QUICKBOOKS_CLIENT_ID',
'client_secret' => 'QUICKBOOKS_CLIENT_SECRET',
'scope' => 'com.intuit.quickbooks.accounting',
],
];
it('returns a data service configured to request oauth token when token is empty', function () {
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnFalse();
$this->token_mock = Mockery::mock(Token::class);
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnFalse();
$this->client = $this->makeClient();
}
expect(
$this->client
->getDataService()
->getServiceContext()
->IppConfiguration->Security->isAccessTokenSet(),
)->toBeFalse();
});
private function makeClient($configs = null)
{
if (! is_null($configs)) {
return new Client($configs, $this->token_mock);
}
it('caches the data service once it is made', function () {
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnFalse();
return new Client($this->configs, $this->token_mock);
}
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnFalse();
#[Test]
public function it_can_be_constructed()
{
$this->assertInstanceOf(Client::class, $this->client);
}
expect($this->client->getDataService())->toBeInstanceOf(DataService::class);
});
#[Test]
public function it_returns_a_data_service_configured_to_request_oauth_token_when_token_is_empty()
{
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnFalse();
it('returns a data service with oauth token when valid access token exists', function () {
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnTrue();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnFalse();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('access_token')
->andReturn('access_token');
$this->assertFalse(
$this->client
->getDataService()
->getServiceContext()
->IppConfiguration->Security->isAccessTokenSet(),
);
}
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('realm_id')
->andReturn('realm_id');
#[Test]
public function it_caches_the_data_service_once_it_is_made()
{
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnFalse();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('refresh_token')
->andReturn('refresh_token');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnFalse();
expect(
$this->client
->getDataService()
->getServiceContext()
->IppConfiguration->Security->isAccessTokenSet(),
)->toBeTrue();
});
// TODO: Find out if this is the actual goal of this test, to make sure a DataService is received
$this->assertEquals(DataService::class, get_class($this->client->getDataService()));
}
it('returns a data service with refreshed token when access token expired but refresh token valid', function () {
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnFalse();
#[Test]
public function it_returns_a_data_service_with_oauth_token_when_valid_access_token_exist()
{
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnTrue();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnTrue();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('access_token')
->andReturn('access_token');
$this->token_mock
->shouldReceive('getAttribute')
->never()
->with('access_token');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('realm_id')
->andReturn('realm_id');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('realm_id')
->andReturn('realm_id');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('refresh_token')
->andReturn('refresh_token');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('refresh_token')
->andReturn('refresh_token');
$this->assertTrue(
$this->client
->getDataService()
->getServiceContext()
->IppConfiguration->Security->isAccessTokenSet(),
);
}
$this->client->getDataService();
})->throws(ServiceException::class);
#[Test]
public function it_returns_a_data_service_with_refreshed_token_when_access_token_expired_but_refresh_token_valid()
{
$this->expectException(ServiceException::class);
it('returns a report service using the data service')
->skip('Once we figure out how to test around the static DataService::Configure');
$this->token_mock
->shouldReceive('getAttribute')
->twice()
->with('hasValidAccessToken')
->andReturnFalse();
it('has logging off by default')
->skip('Have to figure out how to test this with the new code in 5.x');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnTrue();
it('allows logging turned on and pointed to expected file')
->skip('Have to figure out how to test this with the new code in 5.x');
$this->token_mock
->shouldReceive('getAttribute')
->never()
->with('access_token');
it('returns self after deleting token', function () {
$this->token_mock
->shouldReceive('remove')
->once()
->withNoArgs()
->andReturnSelf();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('realm_id')
->andReturn('realm_id');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('refresh_token')
->andReturn('refresh_token');
// TODO: This really needs to test that things are getting called correctly on the DataService class
$this->assertFalse(
$this->client
->getDataService()
->getServiceContext()
->IppConfiguration->Security->isAccessTokenSet(),
);
}
#[Test]
public function it_returns_a_report_service_using_the_data_service()
{
$this->markTestSkipped(
'Once we figure out how to test around the static DataService::Configure',
);
}
#[Test]
public function it_has_logging_off_by_default()
{
$this->markTestSkipped('Have to figure out how to test this with the new code in 5.x');
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidAccessToken')
->andReturnFalse();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnFalse();
$this->assertFalse(
filter_var(
$this->client->getDataService()->getServiceContext()->IppConfiguration->Logger
->RequestLog->EnableRequestResponseLogging,
FILTER_VALIDATE_BOOLEAN,
),
);
}
#[Test]
public function it_allows_logging_turned_on_and_pointed_to_expected_file()
{
$this->markTestSkipped('Have to figure out how to test this with the new code in 5.x');
$this->client = $this->makeClient(
array_merge(
[
'logging' => [
'enabled' => true,
'location' => '/some/valid/path',
],
],
$this->configs,
),
);
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidAccessToken')
->andReturnFalse();
$this->token_mock
->shouldReceive('getAttribute')
->once()
->with('hasValidRefreshToken')
->andReturnFalse();
$this->assertTrue(
filter_var(
$this->client->getDataService()->getServiceContext()->IppConfiguration->Logger
->RequestLog->EnableRequestResponseLogging,
FILTER_VALIDATE_BOOLEAN,
),
);
$this->assertEquals(
'/some/valid/path',
$this->client->getDataService()->getServiceContext()->IppConfiguration->Logger
->RequestLog->ServiceRequestLoggingLocation,
);
}
#[Test]
public function it_returns_self_after_deleting_token()
{
$this->token_mock
->shouldReceive('remove')
->once()
->withNoArgs()
->andReturnSelf();
$this->assertInstanceOf(Client::class, $this->client->deleteToken());
}
}
function dir($path)
{
return $path === '/some/valid/path';
}
function route($name)
{
return $name;
}
expect($this->client->deleteToken())->toBeInstanceOf(Client::class);
});