How to force test fail in Codeception - php

I am making WebServices tests with Codeception, here is my code:
//Making first query for getting needed parameter
$I->wantTo('Make something');
$I->sendPOST($this->route, [
'token' => Fixtures::get('token'),
'id' => Fixtures::get('some_id')
]);
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
$I->seeResponseContains('"error_message"');
//Then I get what I need with regexp
if (preg_match('/^.*\s(?<value_i_need>\d+)\.$/', $I->grabDataFromResponseByJsonPath('$.status.error_message')[0], $matches)) {
$I->sendPOST($this->route, [
'token' => Fixtures::get('token'),
'id' => Fixtures::get('some_id')
]);
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
$I->seeResponseContains('"something"');
$I->seeResponseContains('"something_else"');
} else {
//And if I don't get needed parameter with regular expression, here I have to force test fail
}
Does anybody know how to force test fail?
Thanks in advance!

You can use the fail action on your actor. i.e.
$I->fail('this test fails... just because');

You can mark your test as skipped or incomplete. Here is example:
public function tryToMakeAwesomeThings(ApiTester $I, Scenario $scenario)
{
if ( ... ) {
// do something
} elseif ( ... ) {
$scenario->incomplete();
} elseif ( ... ) {
$scenario->skip();
}
}

You can use PHPUnit\Framework\Assert::fail() or throw PHPUnit\Framework\AssertionFailedError exception.

There is another problem with Codeception 4 and upper.
Method fail now in Assertion module https://codeception.com/docs/modules/Asserts
This module not included in codeception by default, you must install it separately.
When you install it, this module must be added in config file, such as "api.suite.yml"
actor: ApiTester
modules:
enabled:
...
- \Codeception\Module\Asserts
After this, you need to rebuild codeception and this method appear to use.

Use assertInstanceOf() from https://codeception.com/docs/modules/Asserts
Example:
public function test_array_result() {
/** #var Demo $demo */
$demo = Common::get_which_class( $this->post_id );
self::assertInstanceOf( Demo::class, $demo, 'Unable to instantiate the Demo class.');
$x = [ 'a', 'b', 'c' ];
$this->assertEqualSetsWithIndex( $x, $demo->get_result() );
}

Related

Using separate data provider class with PHPUnit and attributes

I would like to separate Tests and Data Providers. Using PHP 8 attributes, I cannot get the following test to run when referencing an external Data Provider:
#[Test]
#[DataProviderExternal(RouterDataProvider::class, 'registerGetRouteData')]
public function itRegistersGetRoute(Route $route, array $expectedResult)
{
$this->router->get($route);
$this->assertEquals($expectedResult, $this->router->getRoutes());
}
My data provider class:
class RouterDataProvider
{
public static function registerGetRouteData(): array
{
return [
$route = new Route('/', ['IndexController', 'index']),
[
'GET' => [
'/' => $route,
],
'POST' => []
]
];
}
}
How could I get this test to run with the desired provider method?
By running PHPUnit with the following flags, I was able to see exactly what my issue was:
./vendor/bin/phpunit --display-deprecations --display-warnings --diplay-errors --display-notices
The data set was invalid. Changing the return to yield and updating the return type for the registerGetRouteData method from array to \Generator resolved this.
I was running phpunit with the --testdox flag, so I'm not sure if this is what stopped me seeing any errors initially and assume the test was being skipped.

How to implement Laravel password validation rules as string in arrays?

I want to use the out-of-the-box Laravel password validation rules. This is possible by using the use Illuminate\Validation\Rules\Password class.
So you can use those rules like so:
\Illuminate\Validation\Rule\Password::min(8)->letters()->numbers()->mixedCase()->uncompromised(3)
However, you can't use those rules inside a config file, like in this package because you get the following error when running
artisan config:cache
❯ artisan config:cache > ─╯
Configuration cache cleared successfully.
LogicException
Your configuration files are not serializable.
at vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigCacheCommand.php:84
80▕ require $configPath;
81▕ } catch (Throwable $e) {
82▕ $this->files->delete($configPath);
83▕
➜ 84▕ throw new LogicException('Your configuration files are not serializable.', 0, $e);
85▕ }
86▕
87▕ $this->info('Configuration cached successfully.');
88▕ }
1 bootstrap/cache/config.php:859
Error::("Call to undefined method Illuminate\Validation\Rules\Password::__set_state()")
After reading the docs, it seems that you can't use those password rules as strings inside an array like so:
"password_rules" => ['min:8'],
So checking the available rules, the rest of the following rules (letters, mixedCase, uncompromised) are not available.
Then, is there a workaround to set those password rules inside the config file?
(so that, I can avoid the LogicException: Your configuration files are not serializable. after executing the artisan config:cache command).
use Illuminate\Validation\Rules\Password;
$rules = [
'password' => [
'required',
'string',
Password::min(8)
->mixedCase()
->numbers()
->symbols()
->uncompromised(),
'confirmed'
],
]
Try using the above code it should work.
I am not a Laravel person, but you can put the config into an array and then unpack it later in your app with something like this:
// for illustration
class Mock {
public function __call($name, $args) {
printf("called: %s(%s)\n", $name, implode(', ', $args));
return $this;
}
// this is awful. never do this.
// in Laravel the min() function, and only this function, is basically
// an alias to the constructor, which is weird.
public static function __callStatic($name, $args) {
return (new self())->$name(...$args);
}
}
$params = [
'min' => [8],
'letters' => [],
'numbers' => [],
'mixedCase' => [],
'uncompromised' => [3]
];
$o = NULL;
foreach( $params as $func => $args ) {
if( is_null($o) ) {
$o = Mock::$func(...$args);
} else {
$o = $o->$func(...$args);
}
}
Output:
called: min(8)
called: letters()
called: numbers()
called: mixedCase()
called: uncompromised(3)

PHPUnit how to write a Laravel Nova Observer test

I would like to write a test for my CommentObserver. This observer is only registered in the NovaServiceProvider but not the AppServiceProvider. This means I cannot test my observer by using my own Controllers.
In my eyes I have 3 ways to test my observer:
Either performing a feature test by sending a post request to the Nova API
Mocking the observer by calling the function in the observer to check if the function perfoms as desired
Trying to register my observer on the fly in the AppServiceProvider, performing a request and deregistering the observer in the AppServiceProvider again.
I tried to find a solution for any of these 3 ways to test my observer but unfortunately I faild with any of them.
Problems:
For way 1 I always get a validation error and Nova tells me that my input is invalid.
For way 2 I fail at mocking the observer function
For way 3 I didn't find any solution on how to register and deregister the oberserver on the fly at the AppServiceProvider
Do you guys have idea and solition on how I can test my CommentObserver (which is as written above only registered in my NovaServiceProvider).
Update:
So, here is the code of my observer. I need to have an valid request to test my observer in order to have the ability to access the $request->input('images') variable. I do know I can also use $comment->content instead of request()->input('content') because $comment->content already contains the new content which is not saved it this point.
The reason why I need a valid request is that the variable images is not part of the Comment model. So I cannot use $comment->images because it simply doesn't exist. That's why I need to access the request input. What my observer is basically doing is to extract the base64 images from the content, saves them to the server and replaces them by an image link.
class CommentObserver
{
public function updating(Comment $comment)
{
if (!request()->input('content')) {
return;
}
if (request()->input('content') == $comment->getRawOriginal('content')) {
return;
}
$images = request()->input('images');
if(!is_array($images)) {
$images = json_decode(request()->input('images'));
}
checkExistingImagesAndDeleteWhenNotFound($comment, request()->input('content'), 'comments', 'medium');
$comment->content = addBase64ImagesToModelFromContent($comment, request()->input('content'), $images, 'comments', 'medium');
}
}
This is my test so far. I choose way 1 but as described already this always leads to an validation error by the nova controller and I cannot figure out what is the error/what is missing or wrong.
class CommentObserverTest extends TestCase
{
/** #test */
public function it_test()
{
$user = User::factory()->create([
'role_id' => Role::getIdByName('admin')
]);
$product = Product::factory()->create();
$comment = Comment::factory()->create(['user_id' => $user->id, 'content' => '<p>Das ist wirklich ein super Preis!</p>', 'commentable_type' => 'App\Models\Product', 'commentable_id' => $product->id]);
$data = [
'content' => '<p>Das ist wirklich ein HAMMER Preis!</p>',
'contentDraftId' => '278350e2-1b6b-4009-b4a5-05b92aedaae6',
'pageStatus' => PageStatus::getIdByStatus('publish'),
'pageStatus_trashed' => false,
'commentable' => $product->id,
'commentable_type' => 'App\Models\Product',
'commentable_trashed' => false,
'user' => $user->id,
'user_trashed' => false,
'_method' => 'PUT',
'_retrieved_at' => now()
];
$this->actingAs($user);
$response = $this->put('http://nova.mywebsiteproject.test/nova-api/comments/' . $comment->id, $data);
dd($response->decodeResponseJson());
$das = new CommentObserver();
}
}
Kind regards and thank you
Why depend on the boot method in your NovaServiceProvider? It is possible to call the observe() method on the fly in your test:
class ExampleTest extends TestCase
{
/** #test */
public function observe_test()
{
Model::observe(ModelObserver::class);
// If you need the request helper, you can add input like so:
request()->merge([
'content' => 'test'
]);
// Fire model event by updating model
$model->update([
'someField' => 'someValue',
]);
// Updating should be triggered in ModelObserver
}
}
It should be now be possible in your observer class:
public function updating(Model $model)
{
dd(request()->input('content')); // returns 'test'
}

Dingo/API when Unit Testing: The version given was unknown or has no registered routes

I built an API using dingo/api 0.10.0, Laravel 5.1 and lucadegasperi/oauth2-server-laravel": "^5.1".
All my routes work fine in Postman/Paw!
The problem appears when I try to test the API using PHPUnit.
This is part of my route-api.php file
<?php
$api = app('Dingo\Api\Routing\Router');
$api->version(['v1'], function ($api) {
$api->post('oauth/access_token', function () {
return response(
\LucaDegasperi\OAuth2Server\Facades\Authorizer::issueAccessToken()
)->header('Content-Type', 'application/json');
});
$api->group(['middleware' => ['oauth', 'api.auth']], function ($api) {
$api->post('/register', 'YPS\Http\Controllers\Api\UserController#register');
});
And this is my test file UserRegistrationTest.php
class UserRegistrationTest extends ApiTestCase
{
public function setUp()
{
parent::setUp();
parent::afterApplicationCreated();
}
public function testRegisterSuccess()
{
$data = factory(YPS\User::class)->make()->toArray();
$data['password'] = 'password123';
$this->post('api/register', $data, $this->headers)
->seeStatusCode(201)
->seeJson([
'email' => $data['email'],
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
]);
}
public function testRegisterMissingParams()
{
$this->post('api/register', [], $this->headers, $this->headers, $this->headers)->seeStatusCode(422);
}
}
The ApiTestCase simply retrieves a token and sets the headers.
private function setHeaders()
{
$this->headers = [
'Accept' => 'application/vnd.yps.v1+json',
'Authorization' => 'Bearer ' . $this->OAuthAccessToken,
];
}
Now, the weird part is that the first test testRegisterSuccess runs perfectly and returns the response I expect. But the second one testRegisterMissingParams, even though it's the same route, returns this,
array:2 [
"message" => "The version given was unknown or has no registered routes."
"status_code" => 400
]
I tracked the error and it is in the Laravel adapter here:
public function dispatch(Request $request, $version)
{
// it seems that the second time around can't find any routes with the key 'v1'
if (! isset($this->routes[$version])) {
throw new UnknownVersionException;
}
$routes = $this->mergeExistingRoutes($this->routes[$version]);
$this->router->setRoutes($routes);
return $this->router->dispatch($request);
}
And further more, if i run one test at a time (eg comment one out, run test and then comment the other and run test) i see the result expected in both tests. The problem is when i run multiple tests.
Any thoughts on that?
Thank you!
Run php artisan api:routes to see full path you may have missed something for the URL, also if this working if you request your URL manually?
I had same problem with testing using Dingo & Lumen. This worked for me - remove bootstrap="bootstrap/app.php" from phpunit.xml file and change line processIsolation="false" to processIsolation="true".

Unit Test Laravel's FormRequest

I am trying to unit test various custom FormRequest inputs. I found solutions that:
Suggest using the $this->call(…) method and assert the response with the expected value (link to answer). This is overkill, because it creates a direct dependency on Routing and Controllers.
Taylor’s test, from the Laravel Framework found in tests/Foundation/FoundationFormRequestTest.php. There is a lot of mocking and overhead done there.
I am looking for a solution where I can unit test individual field inputs against the rules (independent of other fields in the same request).
Sample FormRequest:
public function rules()
{
return [
'first_name' => 'required|between:2,50|alpha',
'last_name' => 'required|between:2,50|alpha',
'email' => 'required|email|unique:users,email',
'username' => 'required|between:6,50|alpha_num|unique:users,username',
'password' => 'required|between:8,50|alpha_num|confirmed',
];
}
Desired Test:
public function testFirstNameField()
{
// assertFalse, required
// ...
// assertTrue, required
// ...
// assertFalse, between
// ...
}
public function testLastNameField()
{
// ...
}
How can I unit test (assert) each validation rule of every field in isolation and individually?
I found a good solution on Laracast and added some customization to the mix.
The Code
/**
* Test first_name validation rules
*
* #return void
*/
public function test_valid_first_name()
{
$this->assertTrue($this->validateField('first_name', 'jon'));
$this->assertTrue($this->validateField('first_name', 'jo'));
$this->assertFalse($this->validateField('first_name', 'j'));
$this->assertFalse($this->validateField('first_name', ''));
$this->assertFalse($this->validateField('first_name', '1'));
$this->assertFalse($this->validateField('first_name', 'jon1'));
}
/**
* Check a field and value against validation rule
*
* #param string $field
* #param mixed $value
* #return bool
*/
protected function validateField(string $field, $value): bool
{
return $this->validator->make(
[$field => $value],
[$field => $this->rules[$field]]
)->passes();
}
/**
* Set up operations
*
* #return void
*/
public function setUp(): void
{
parent::setUp();
$this->rules = (new UserStoreRequest())->rules();
$this->validator = $this->app['validator'];
}
Update
There is an e2e approach to the same problem. You can POST the data to be checked to the route in question and then see if the response contains session errors.
$response = $this->json('POST',
'/route_in_question',
['first_name' => 'S']
);
$response->assertSessionHasErrors(['first_name']);
I see this question has a lot of views and misconceptions, so I will add my grain of sand to help anyone who still has doubts.
First of all, remember to never test the framework, if you end up doing something similar to the other answers (building or binding a framework core's mock (disregard Facades), then you are doing something wrong related to testing).
So, if you want to test a controller, the always way to go is: Feature test it. NEVER unit test it, not only is cumbersome to unit test it (create a request with data, maybe special requirements) but also instantiate the controller (sometimes it is not new HomeController and done...).
They way to solve the author's problem is to feature test like this (remember, is an example, there are plenty of ways):
Let's say we have this rules:
public function rules()
{
return [
'name' => ['required', 'min:3'],
'username' => ['required', 'min:3', 'unique:users'],
];
}
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class HomeControllerTest extends TestCase
{
use RefreshDatabase;
/*
* #dataProvider invalid_fields
*/
public function test_fields_rules($field, $value, $error)
{
// Create fake user already existing for 'unique' rule
User::factory()->create(['username' => 'known_username']);
$response = $this->post('/test', [$field => $value]);
$response->assertSessionHasErrors([$field => $error]);
}
public function invalid_fields()
{
return [
'Null name' => ['name', null, 'The name field is required.'],
'Empty name' => ['name', '', 'The name field is required.'],
'Short name' => ['name', 'ab', 'The name must be at least 3 characters.'],
'Null username' => ['username', null, 'The username field is required.'],
'Empty username' => ['username', '', 'The username field is required.'],
'Short username' => ['username', 'ab', 'The username must be at least 3 characters.'],
'Unique username' => ['username', 'known_username', 'The username has already been taken.'],
];
}
}
And that's it... that is the way of doing this sort of tests... No need to instantiate/mock and bind any framework (Illuminate namespace) class.
I am taking advantage of PHPUnit too, I am using data providers so I don't need to copy paste a test or create a protected/private method that a test will call to "setup" anything... I reuse the test, I just change the input (field, value and expected error).
If you need to test if a view is being displayed, just do $response->assertViewIs('whatever.your.view');, you can also pass a second attribute (but use assertViewHas) to test if the view has a variable in it (and a desired value). Again, no need to instantiate/mock any core class...
Have in consideration this is just a simple example, it can be done a little better (avoid copy pasting some errors messages).
One last important thing: If you unit test this type of things, then, if you change how this is done in the back, you will have to change your unit test (if you have mocked/instantiated core classes). For example, maybe you are now using a FormRequest, but later you switch to other validation method, like a Validator directly, or an API call to other service, so you are not even validating directly in your code. If you do a Feature Test, you will not have to change your unit test code, as it will still receive the same input and give the same output, but if it is a Unit Test, then you are going to change how it works... That is the NO-NO part I am saying about this...
Always look at test as:
Setup minimum stuff (context) for it to begin with:
What is your context to begin with so it has logic ?
Should a user with X username already exist ?
Should I have 3 models created ?
Etc.
Call/execute your desired code:
Send data to your URL (POST/PUT/PATCH/DELETE)
Access a URL (GET)
Execute your Artisan Command
If it is a Unit Test, instantiate your class, and call the desired method.
Assert the result:
Assert the database for changes if you expected them
Assert if the returned value matches what you expected/wanted
Assert if a file changed in any desired way (deletion, update, etc)
Assert whatever you expected to happen
So, you should see tests as a black box. Input -> Output, no need to replicate the middle of it... You could setup some fakes, but not fake everything or the core of it... You could mock it, but I hope you understood what I meant to say, at this point...
Friends, please, make the unit-test properly, after all, it is not only rules you are testing here, the validationData and withValidator functions may be there too.
This is how it should be done:
<?php
namespace Tests\Unit;
use App\Http\Requests\AddressesRequest;
use App\Models\Country;
use Faker\Factory as FakerFactory;
use Illuminate\Routing\Redirector;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;
use function app;
use function str_random;
class AddressesRequestTest extends TestCase
{
public function test_AddressesRequest_empty()
{
try {
//app(AddressesRequest::class);
$request = new AddressesRequest([]);
$request
->setContainer(app())
->setRedirector(app(Redirector::class))
->validateResolved();
} catch (ValidationException $ex) {
}
//\Log::debug(print_r($ex->errors(), true));
$this->assertTrue(isset($ex));
$this->assertTrue(array_key_exists('the_address', $ex->errors()));
$this->assertTrue(array_key_exists('the_address.billing', $ex->errors()));
}
public function test_AddressesRequest_success_billing_only()
{
$faker = FakerFactory::create();
$param = [
'the_address' => [
'billing' => [
'zip' => $faker->postcode,
'phone' => $faker->phoneNumber,
'country_id' => $faker->numberBetween(1, Country::count()),
'state' => $faker->state,
'state_code' => str_random(2),
'city' => $faker->city,
'address' => $faker->buildingNumber . ' ' . $faker->streetName,
'suite' => $faker->secondaryAddress,
]
]
];
try {
//app(AddressesRequest::class);
$request = new AddressesRequest($param);
$request
->setContainer(app())
->setRedirector(app(Redirector::class))
->validateResolved();
} catch (ValidationException $ex) {
}
$this->assertFalse(isset($ex));
}
}

Categories