nyholm/nsa
Testing helper to access and manipulate private/protected properties and methods in PHP. Set/get instance or static properties and invoke hidden methods to simplify tests and improve DX. Install via Composer: nyholm/nsa.
Installation:
composer require --dev nyholm/nsa
Ensure it’s listed under require-dev in composer.json to block production inclusion.
First Use Case: Test a private method in a Laravel Eloquent model or service. For example:
use Nyholm\NSA\NSA;
$user = new User();
NSA::setProperty($user, 'email_verified_at', now());
$result = NSA::invokeMethod($user, 'validateVerification', ['test@example.com']);
$this->assertTrue($result);
Where to Look First:
Testing Private Model Methods:
// Test a private model method (e.g., validation logic)
$user = new User();
NSA::setProperty($user, 'password', 'plaintext');
$hashed = NSA::invokeMethod($user, 'hashPassword');
$this->assertTrue(password_verify('plaintext', $hashed));
Static Property Access:
// Test static properties in abstract classes (e.g., Laravel base models)
$incrementing = NSA::getProperty('App\Models\Model', 'incrementing');
$this->assertFalse($incrementing);
Bulk Property Inspection:
// Debug object state during test development
$properties = NSA::getProperties($user);
$this->assertArrayHasKey('api_token', $properties);
Method Argument Handling:
// Invoke private methods with arguments
$result = NSA::invokeMethod(
$service,
'processOrder',
['order_id' => 123, 'status' => 'pending']
);
Caching for Performance:
// Cache method closures to avoid reflection overhead in loops
$closure = NSA::getClosure($object, 'privateMethod');
foreach ($data as $item) {
$closure($item); // Faster than repeated NSA::invokeMethod
}
TDD for Private Logic:
private function calculateTax() in a service).Legacy Code Refactoring:
User model by extracting private methods and testing them with NSA.Fixture Setup:
// Set up complex test data
$user = new User();
NSA::setProperty($user, 'trial_ends_at', now()->addDays(7));
NSA::setProperty($user, 'is_admin', true);
Static Analysis:
// Test static constants or properties in base classes
$version = NSA::getConstant('App\Services\ApiService', 'VERSION');
$this->assertEquals('1.0.0', $version);
Laravel Service Container:
$service = app()->make(PrivateService::class);
$result = NSA::invokeMethod($service, 'internalLogic');
Pest PHP:
it('tests private method', function () {
$user = new User();
NSA::setProperty($user, 'name', 'Test User');
expect(NSA::invokeMethod($user, 'getFullName'))->toBe('Test User');
});
Avoid in Production:
composer.json constraints:
"config": {
"preferred-install": "dist",
"sort-packages": true
},
"minimum-stability": "dev",
"require-dev": {
"nyholm/nsa": "^1.3"
}
phpunit.xml):
<env name="NSA_ENABLED" value="true" />
Document Usage:
// NSA: Testing private method due to legacy code constraints
$result = NSA::invokeMethod($model, 'legacyValidation');
Reflection Limitations:
__get/__set workarounds).PHP Version Quirks:
Static Method Calls:
NSA::getProperty('AbstractModel', 'property')), but not for abstract static methods.Performance:
Testing Anti-Patterns:
Method Not Found:
App\Models\User vs. User).Property Access Errors:
__get).\App\Models\User).Argument Mismatches:
NSA::getClosure() to inspect method signatures:
$closure = NSA::getClosure($object, 'methodName');
$closure->getParameters(); // Debug expected arguments
CI Failures:
webmozart/assert to ^1.0 to avoid breaking changes:
composer require webmozart/assert:^1.0
Combine with Mockery:
$mock = Mockery::mock(User::class)->makePartial();
$mock->shouldReceive('privateMethod')->andReturn('mocked');
NSA::invokeMethod($mock, 'privateMethod'); // Returns 'mocked'
Test Static Constants:
$this->assertEquals(
'1.0.0',
NSA::getConstant('App\Services\ApiService', 'VERSION')
);
Laravel-Specific:
NSA::setProperty($user, 'attribute', 'value');
$this->assertEquals('value', $user->getAttribute('attribute'));
$provider = new AuthServiceProvider(app());
NSA::invokeMethod($provider, 'boot');
Extension Points:
function assertPrivateMethodReturns($object, string $method, $expected) {
$this->assertEquals($expected, NSA::invokeMethod($object, $method));
}
Performance Optimization:
beforeEach(function () {
$this->privateMethod = NSA::getClosure($this->object, 'privateMethod');
});
Avoid in Critical Paths:
How can I help you explore Laravel packages today?