Fixtures' references disapearing after one test - php

In my LoadFixture.php, I add reference to all my fixtures like this :
public function load(ObjectManager $manager) {
$user = new user("Dummy");
$this->persist($user);
$this->addReference("user", $user);
}
In my test class I load them like this :
public function setUp() {
if(self::$do_setup){
$this->loadFixtures(array(
"Bundle\\Tests\\Fixtures\\LoadUser"
)) ;
}
}
In my tests I use them like this :
public function testOne() {
$client = $this->createClient($this->getReference("user_a"));
$client->request('GET', '/');
$this->assertStatusCode(200, $client);
self::$do_setup=false;
}
public function testTwo() {
$client = $this->createClient($this->getReference("user_a"));
$client->request('GET', '/home');
$this->assertStatusCode(200, $client);
}
The thing is, technically, I dont need to use setUp() for each test, so I use $do_setup and a if to execute setUp if needed.
But if I dont execute the setUp() in my testTwo, while my fixtures are in my database, $this->getReference("user_a") is giving me an error :
Call to a member function getReferenceRepository() on a non-object
How can I solve that ?
UPDATE
I have found a solution. So I post it here, just in case someone face the same problem as me.
Many thanks to #Damien Flament for his answer, regarding the fact that the TestCase is deleted after each test.
I changed the name of my setUp() method to open(), and my tearDown() method to close().
The first method of the class call the open() method, and now return $this.
The next method is annoted #depends testOne and take a parameter.
With this parameter I can use my references again.
Ex :
// new setUp Metod
public function open() {
if(self::$do_setup){
$this->loadFixtures(array(
"Bundle\\Tests\\Fixtures\\LoadUser"
)) ;
}
}
//new tearDown method
public function close() {
$this->getContainer()->get('doctrine.orm.entity_manager')->getConnection()->close();
}
public function testOne() {
$this->open();
$client = $this->createClient($this->getReference("user_a"));
$client->request('GET', '/');
$this->assertStatusCode(200, $client);
return $this;
}
/**
* #depends testOne
*/
public function testTwo($it) {
$client = $this->createClient($it->getReference("user_a"));
$client->request('GET', '/home');
$this->assertStatusCode(200, $client);
return $it;
}
/**
* #depends testTwo
*/
public function testThree($it) {
$client = $this->createClient($it->getReference("user_a"));
$client->request('GET', '/about');
$this->assertStatusCode(200, $client);
$this->close();
}

I think the TestCase object is deleted and recreated by PHPUnit (I didn't read the PHPUnit source code, but I think it's the more easy way to reset the testing environment for each test).
So your object (probably referenced by a test class object attribute) is probably garbage collected.
To setup fixture once per test class, use the TestCase::setUpBeforeClass() method.
See documention on "Sharing fixtures".

Related

Which class should i mock service class or third party api of service class?

PostController's store method which calls the service class and service class calls the third party api i.e. line. while storing a post. i want to write testcase if the notify field is true then it sends notification to the user's through line if not then return with error message. i am not getting any idea how to perform this test. here is the code for PostController.php
private Post $post;
private PostService $service;
public function __construct(
PostService $service,
Post $post
) {
$this->service = $service;
$this->post = $post;
}
public function store(PostRequest $request): RedirectResponse
{
$post = $this->service->createPost($request->validated());
if ($request->notify) {
$message = 'lorem ipsum';
$this->service->lineSendToGroup($request->category_big_id, $message);
}
return redirect()->to('/posts')->with('success_message', 'Post created successfully.');
}
PostService.php
use App\Library\Line;
use App\Models\CategoryBig;
class PostService
{
private CategoryBig $categoryBig;
public function __construct(
CategoryBig $categoryBig,
) {
$this->categoryBig = $categoryBig;
}
public function lineSendToGroup(int $categoryBigId, string $message): void
{
$catB = $this->findOrFailCategoryBig($categoryBigId);
Line::send(
$catB->line_message_channel_secret,
$catB->line_message_channel_access_token,
$catB->line_group_id,
$message
);
}
public function findOrFailCategoryBig(int $categoryBigId): CategoryBig
{
return $this->categoryBig->whereId($categoryBigId)->firstOrFail();
}
public function createPost(array $createData): Post
{
$createData += [$data];
return $this->greeting->create($createData);
}
Line.php
namespace App\Library;
use Illuminate\Support\Carbon;
use LINE\LINEBot;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use LINE\LINEBot\Event\MessageEvent\TextMessage;
use LINE\LINEBot\MessageBuilder\TextMessageBuilder;
use Log;
class Line
{
public static function send($channel_secret, $access_token, $line_user_id, $message)
{
$http_client = new CurlHTTPClient($access_token);
$bot = new LINEBot($http_client, ['channelSecret' => $channel_secret]);
$textMessageBuilder = new TextMessageBuilder($message);
$response = $bot->pushMessage($line_user_id, $textMessageBuilder);
if ($response->isSucceeded()) {
return true;
} else {
return false;
}
}
}
Test
public function test_notification_when_LINE_notification_is_specified()
{
}
Step 1)
You can write a unit test for Line class.
For ease of use, you can refactor this class and use Laravel Http client instead. Create different stubs for each line's response (e.g 200) then use Laravel testing helpers to mock resposne.
Step 2)
Write a feature test for your controller. In this test you don't care about internal functionality of Line class and just want to make sure method of this class is called with proper arguments.
You can use mockery to achieve that.
Migrate to non-static method on Line class. You can't mock static methods.
For happy path test:
$mock = Mockery::mock(\App\Library\Line::class);
$mock->shouldReceive('send')
->once()
->with($secret, $token, $groupId, $message)
->andReturn(true);
// of course mocking object doesn't do much alone and you need to bind and resolve it.
$this->app->instance(\App\Library\Line::class, $mock);
Then resolve it inside your PostService#lineSendToGroup
resolve(Line::class, [
'token' => $token,
// ... other args
])
For opposite scenario use shouldNotReceive('send') instead.

How do I use PHPUnit to test __construct with arguments?

I'm new to PHPUnit, and unit testing in general. I can't seem to find a clear tutorial or resource on how best to test:
Passing no argument fails.
How to pass an argument for a constructor test.
Passing an empty argument results in the expected exception.
How would I approach testing this constructor?
<?php
class SiteManagement {
public function __construct (array $config) {
// Make sure we actually passed a config
if (empty($config)) {
throw new \Exception('Configuration not valid', 100);
}
// Sanity check the site list
if (empty($config['siteList'])) {
throw new \Exception('Site list not set', 101);
}
}
}
The example 2.11 of the PHPUnit documentation shows how to test exceptions.
For your specific class, it would be something like this:
$this->expectException(Exception::class);
$object = new SiteManagement([]);
You shouldn't test if the method fails without arguments unless those arguments are optional. This would be out of the scope for a unit test.
A good test battery for your class would be:
/** #test */
public function shouldFailWithEmptyConfig(): void
{
$config = [];
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Configuration not valid');
$this->expectExceptionCode(100);
new SiteManagement($config);
}
/** #test */
public function shouldFailWithoutSiteListConfig(): void
{
$config = ['a config'];
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Site list not set');
$this->expectExceptionCode(101);
new SiteManagement($config);
}
/** #test */
public function shouldMakeAFuncionality(): void
{
$config = [
'siteList' => '',
];
$siteManagement = new SiteManagement($config);
self::assertSame('expected', $siteManagement->functionality());
}

PHPUnit Mock Function Call

I have the following function within an object that I wish to test
public function getUser($credentials, UserProviderInterface $userProvider)
{
$resourceOwner = $this->getAuthClient()->fetchUserFromToken($credentials);
// ... Code to create user if non-existent, update values, etc.
// ... Basically the code I want to test is here
return $user;
}
The getAuthClient() call returns a Client object with the available function of fetchUserFromToken
How can I, in a PHPUnit test, mock the fetchUserFromToken to just return a ResourceOwner object? Because the actual function does a lot of authentication mechanisms and is out of the scope of this test case
I found a php library called Runkit, but that is not the approach I want to peruse. It feels hacky and overkill for the problem at hand.
The getAuthClient() function is defined as follows
private function getAuthClient() {
return $this->clients->getClient('auth');
}
With $this->clients being defined by the constructor
public function __construct(ClientRepo $clients) {
$this->clients = $clients;
}
So, I mocked the ClientRepo and exposed the getClient() method to return a Mock of the AuthClient (regardless of input) so that I can control the return of fetchUserFromToken() call.
public function testGetUser() {
$client = $this->createMock(WebdevClient::class);
$client->expects($this->any())
->method('fetchUserFromToken')
->will($this->returnCallback(function()
{
// TARGET CODE
}));
$clients = $this->createMock(ClientRegistry::class);
$clients->expects($this->any())
->method('getClient')
->will($this->returnCallback(function() use ($client)
{
return $client;
}));
$object = new TargetObject($clients);
$result = $object->getUser(...);
// ... Assertions to follow
}

PHPUnit - ReturnValueMap of Mocks not finding Mocked Class Methods

I'm new to unit testing and have come across something I don't understand when using returnValueMap() in PHPUnit. I've been googling for days now ...
Consider this code under test;
public function __construct(EntityManager $entityManager, AuditLog $auditLog) {
$this->entityManager = $entityManager;
$this->auditLog = $auditLog;
}
public function updateSomeId($oldId, $newId)
{
$repositories = ['repo1', 'repo2', 'repo3'];
foreach ($repositories as $repository) {
try {
$this->entityManager->getRepository($repository)
->updateSomeId($oldId, $newId);
} catch (RepositoryException $e) {
throw new SomeOtherException($e->getMessage(), $e->getCode(), $e);
}
}
}
The unit test code;
... code removed for brevity
$repoMaintenance = new RepoMaintenance(
$this->getEntityManagerMock(),
$this->getAuditLogMock()
);
$this->assertTrue($repoMaintenance->updateSomeId(
$this->oldId,
$this->newId
));
/**
* #return EntityManager
*/
private function getEntityManagerMock()
{
$entityManagerMock = $this->getMockBuilder(EntityManager::class)
->disableOriginalConstructor()
->getMock();
$entityManagerMock
->method('getRepository')
->willReturn($this->returnValueMap($this->getEntityManagerReturnValueMap()));
return $entityManagerMock;
}
/**
* #return array
*/
private function getEntityManagerReturnValueMap()
{
return [
['repo1', $this->getRepo1Mock()],
['repo2', $this->getRepo2Mock()],
['repo3', $this->getRepo3Mock()],
];
}
/**
* #return Repo1
*/
private function getRepo1Mock()
{
return $this->getMockBuilder(Repo1::class)
->disableOriginalConstructor()
->getMock();
}
... Code removed for brevity
When the unit test is run, the following fatal error is returned;
PHP Fatal error: Call to undefined method PHPUnit_Framework_MockObject_Stub_ReturnValueMap::updateSomeId()
I've previously used mocks in return value maps with no issue accessing methods in a public context. The difference is I'm attempting to mock __construct() variables, which are set to private access within the SUT.
What am I missing? The problem (I would naively guess) is with the private access level of the members being mocked.
Is there a way to unit test this code? I don't want to hit the database at any point and this is the reason for mocking the calls to it.
You should have will($this->returnValueMap... instead of willReturn($this->returnValueMap...

PHPUnit mocked method returns null

I am trying to test the below class using PHPUnit
class stripe extends paymentValidator {
public $apiKey;
public function __construct ($apiKey){
$this->apiKey = $apiKey;
}
public function charge($token) {
try {
return $this->requestStripe($token);
} catch(\Stripe\Error\Card $e) {
echo $e->getMessage();
return false;
}
}
public function requestStripe($token) {
// do something
}
}
My test scripts is like the below:
class paymentvalidatorTest extends PHPUnit_Framework_TestCase
{
/**
* #test
*/
public function test_stripe() {
// Create a stub for the SomeClass class.
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe', 'charge'])
->getMock();
$stripe->expects($this->any())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
}
}
With my test script I was expecting the test double of stripe::charge() method will do exactly as the defined in the original class and the stripe::requestStripe() will return 'Miaw'. Therefore, $stripe->charge('token') should also return 'Miaw'. However, when I run the test I get:
Failed asserting that null matches expected 'Miaw'.
How should I fix this ?
Where you're calling setMethods, you're telling PHPUnit that the mock class should mock the behaviour of those methods:
->setMethods(['requestStripe', 'charge'])
In your case it looks like you want to partially mock the class, so that requestStripe() returns Miaw, but you want charge to run its original code - you should just remove charge from the mocked methods:
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe'])
->getMock();
$stripe->expects($this->once())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
While you're at it you may as well specify how many times you expect requestStripe() to be called - it's an extra assertion with no extra effort, as using $this->any() doesn't provide you with any added benefit. I've included using $this->once() in the example.

Categories