I am using Codeception\Util\Stub to create unit tests. And I want to be sure that my method called several times. For this I am using method 'exactly'.
Example:
use \UnitTester;
use \Codeception\Util\Stub as StubUtil;
class someCest
{
public function testMyTest(UnitTester $I)
{
$stub = StubUtil::makeEmpty('myClass', [
'myMethod' => StubUtil::exactly(2, function () { return 'returnValue'; })
]);
$stub->myMethod();
}
}
As you can see I called myMethod once. But test passed.
The same problem with method ::once , because this method is using the same class PHPUnit_Framework_MockObject_Matcher_InvokedCount ('matcher' below).
Test will fail only if I will call more then expected times ( >2 ). Because matcher's method 'invoked' checks if count more then expected. But can't see if someone call matcher's method 'verify' to check if myMethod called less then expected.
Sorry stackoverflow, this is my first question.
UPDATE
My fast and BAD temporary solution:
Add stub into helper
$I->addStubToVerify($stub);
Add method into helper to validate:
protected $stubsToVerify = [];
public function verifyStubs()
{
foreach ($this->stubsToVerify as $stub) {
$stub->__phpunit_getInvocationMocker()->verify();
}
return $this;
}
Call this method in Cest's method _after():
public function _after(UnitTester $I)
{
$I->verifyStubs();
}
You need to pass $this as a third parameter to makeEmpty:
$stub = StubUtil::makeEmpty('myClass', [
'myMethod' => StubUtil::exactly(2, function () { return 'returnValue'; })
], $this);
Instead of use \Codeception\Util\Stub to Expected::once(), modify your unit tests to extends \Codeception\Test\Unit then use $this->make() or $this->makeEmpty() to create your stubs. It will works as you expect ;)
For example:
class MyProcessorTest extends \Codeception\Test\Unit
{
public function testSomething()
{
$processor = new MyProcessor(
$this->makeEmpty(EntityManagerInterface::class, [
'remove' => Expected::never(),
'persist' => Expected::once(),
'flush' => Expected::once(),
])
);
$something = $this->somethingFactory(Processor::OPERATION_CREATE);
$processor->process($something);
}
}
Cheers!
Looks like your method does not exist in the target class that you mock.
If the method exists then Codeception replaces it with the stub you provide. And if this method does not exist then Codeception adds a field with this name to the stub object.
It is because methods and properties are passed in the same array so Codeception has no other way to tell methods from properties.
So first create a method myMethod in your class myClass.
Related
I would like to wrap an existing service with another one, so, I've found that there is a delegator factory mechanism https://docs.mezzio.dev/mezzio/v3/features/container/delegator-factories/
The service is very simple and I've mostly copy-pasted the example in documentation.
Even without simplification the service looks something like this
class MoneyService {
public function __constructor(LoggerService $logger) {
$this->logger = $logger;
}
}
To check that delegator factory works I've created it like this. Just to be sure that the real service was built correctly.
class MoneyServiceDelegatorFactory
{
public function __invoke(ContainerInterface $container, string $name, callable $callback)
{
var_dump($name, $callback());
die;
}
}
And finally I wire it with configureation
'dependencies' => [
'delegators' => [
MoneyService::class => [
MoneyServiceDelegatorFactory::class,
],
],
],
If I try to check if the container has the MoneyService instance then it is true - $container->has(MoneyService::class) === true.
So, in the delegator factory I expect the result of $callback() to be an instance of a MoneyService class. But instead I get null.
I've figured out what was wrong. I use PHP-DI but in this case I have add a factory which returns an instance of MoneyService. After that, in the next factories, it will be available as a result of $callback()
I'm trying to add a custom assertion to the TestReponse class so I can make something like this:
$response = $this->json('POST', '/foo/bar');
$response->myCustomAssertion();
I tried creating an App\TestResponse class that extends the original one and then binding it in the App\Provider\AppServiceProvider class.
public function register()
{
$this->app->bind('Illuminate\Foundation\Testing\TestResponse', function ($app) {
return new App\TestResponse();
});
}
But $response->json() is still returning the original one and not my own implementation.
How can I extend the TestResponse class?
If you want a little more fine-grained control, you can also extend the Illuminate\Foundation\Testing\TestResponse, as you have done, and then override the createTestResponse method in your TestCase class to return an instance of your custom response class:
// Laravel 8 and above
protected function createTestResponse($response)
{
return tap(App\TestResponse::fromBaseResponse($response), function ($response) {
$response->withExceptions(
$this->app->bound(LoggedExceptionCollection::class)
? $this->app->make(LoggedExceptionCollection::class)
: new LoggedExceptionCollection
);
});
}
// Before Laravel 8
protected function createTestResponse($response)
{
return App\TestResponse::fromBaseResponse($response);
}
From Illuminate\Foundation\Testing\Concerns\MakesHttpRequests.
The TestResponse class uses the Macroable trait so you can add macro functions at runtime.
TestResponse::macro('nameOfFunction', function (...) {
...
});
You can add this to a Service Provider's boot method or somewhere before you need to make the call to that macro'ed method.
I have a class which uses Curl's beforeSend callback. Here it is:
$curl->beforeSend(function() use ($exchanger, $ratesUpdate) {
ExchangerRatesUpdate::create([
'exchanger_id' => $exchanger->id,
'rates_update_id' => $ratesUpdate->id
]);
});
I want to refactor this piece of code a bit by extracting the logic inside callback to its own method, for example: private function beforeSend($exchanger, $ratesUpdate) and call it in $cur->beforeSend.
How can I do this? I found other answers saying that I can pass an array, like [$this, 'beforeSend'], however it throws an error (Creating default object from empty value) and also I couldn't find how can I pass arguments to the method.
This approach works, however it looks like I do double work by passing arguments array twice:
$curl->beforeSend(function() use ($exchanger, $ratesUpdate) {
call_user_func_array([$this, 'beforeXmlFetching'], [
'exchanger' => $exchanger,
'ratesUpdate' => $ratesUpdate
]);
});
I don't know if I answer your question, but in your specific case, you can implement a builder
class ExchangerRatesUpdateBuilder
{
protected $exchanger;
protected $ratesUpdate;
public function build()
{
return ExchangerRatesUpdate::create([
'exchanger_id' => $this->exchanger->id,
'rates_update_id' => $this->ratesUpdate->id
]);
}
public function setExchanger($exchanger)
{
$this->exchanger = $exchanger;
return $this;
}
public function setRatesUpdate($ratesUpdate)
{
$this->ratesUpdate = $ratesUpdate;
return $this;
}
}
In your script, you have to instanciate the builder like this (like a service):
$builder = new ExchangerRatesUpdateBuilder()
When you're calling the $curl->beforeSend() method, you can passe the 'build' callback :
$curl->beforeSend([$builder->setExchanger($exchanger)->setRatesUpdate($ratesUpdate), 'build']);
In a Laravel project I'm working on, I'm wanting to create an API. In that API there will be certain JSON keys that will be required in every request. E.g. a token, or other fields that are ALWAYS required. I am familiar with Laravel's form request feature which allows you to easily create a class with a rules method containing an array of validation logic. However, I am wanting to know if there is a way where I can make one Request class that handles the "always required" fields and then bolt on another request class containing specific field validation for that endpoint.
E.g.
// MasterRequest.php
public function rules() {
return [
'api_key' => 'required|exists:users,api_key',
];
}
// ProductRequest.php
public function rules() {
return [
'product_id' => 'required|integer',
];
}
And then some way always call MasterRequest validation on EVERY api route, and then specify the type of request validation for each route's unique needs?
Is this doable, or even the correct approach?
This is fairly easy to set up, use OOP properties of PHP.
The easiest way (and obvious one):
Make yourself "master class for always required fields" you can also declare it as abstract class.
File AlwaysRequired.php
abstract class AlwaysRequired extends FormRequest
{
public function rules() {
return [
'api_key' => 'required|exists:users,api_key',
];
}
}
and ProductRequest.php
class ProductRequest extends AlwaysRequired
{
public function rules() {
return array_merge(parent::rules(),
['product_id' => 'required|integer']);
}
}
Array merge on php.net
The property way:
Make yourself master class in which you will declare property with "always required" rules and then just array_merge(array,...) it in child class (just like example above).
The "hardest" and most confusing way, but fully automatic:
You can leverage magic functions and method/property visibility of PHP language.
Again make yourself a master class in which you will have a protected property with rules array and implementation for __call() magic method.
Note: You can test code below in interactive shell of PHP $php -a and copy-paste the code.
abstract class A { // Master class
protected $rules = ['abc' => 'required'];
function __call($name, $arg) {
if(method_exists($this, 'rules')){
return array_merge($this->rules, $this->rules());
} else {
//or handle any other method here...
die(var_dump($name, $arg));
}
}
}
class B extends A { //Generic class just like ProductRequest...
protected function rules() { // function must be declared as protected! So its invisible for outside world.
return ['def' => 'required'];
}
}
$b = new B();
var_dump($b->rules());
How it works?
Laravel behind the scenes tries to run rules() method on the request class you specify (in your case ProductRequest), declaring it as protected means that it can not be called except from itself or by another child, which means that __call() method is called instead which is declared in abstract parent class. __call() method simply identifies if caller wanted to call non-existent (because of protected visibility is set) method rules() if that is so it merges the child's rules() result with $rules and returns it.
Checking for correct API key should be handled in Middleware.
You can manually execute Request classes one by one:
public function store()
{
try{
app(MasterRequest::class);
} finally {
app(ProductRequest::class);
}
/*... */
}
I'm creating a Laravel controller where a Random string generator interface gets injected to one of the methods. Then in AppServiceProvider I'm registering an implementation. This works fine.
The controller uses the random string as input to save data to the database. Since it's random, I can't test it (using MakesHttpRequests) like so:
$this->post('/api/v1/do_things', ['email' => $this->email])
->seeInDatabase('things', ['email' => $this->email, 'random' => 'abc123']);
because I don't know what 'abc123' will be when using the actual random generator. So I created another implementation of the Random interface that always returns 'abc123' so I could assert against that.
Question is: how do I bind to this fake generator at testing time? I tried to do
$this->app->bind('Random', 'TestableRandom');
right before the test, but it still uses the actual generator that I register in AppServiceProvider. Any ideas? Am I on the wrong track completely regarding how to test such a thing?
Thanks!
You have a couple options:
Use a conditional to bind the implementation:
class AppServiceProvider extends ServiceProvider {
public function register() {
if($this->app->runningUnitTests()) {
$this->app->bind('Random', 'TestableRandom');
} else {
$this->app->bind('Random', 'RealRandom');
}
}
}
Second option is to use a mock in your tests
public function test_my_controller () {
// Create a mock of the Random Interface
$mock = Mockery::mock(RandomInterface::class);
// Set our expectation for the methods that should be called
// and what is supposed to be returned
$mock->shouldReceive('someMethodName')->once()->andReturn('SomeNonRandomString');
// Tell laravel to use our mock when someone tries to resolve
// an instance of our interface
$this->app->instance(RandomInterface::class, $mock);
$this->post('/api/v1/do_things', ['email' => $this->email])
->seeInDatabase('things', [
'email' => $this->email,
'random' => 'SomeNonRandomString',
]);
}
If you decide to go with the mock route. Be sure to checkout the mockery documentation:
http://docs.mockery.io/en/latest/reference/expectations.html
From laracasts
class ApiClientTest extends TestCase
{
use HttpMockTrait;
private $apiClient;
public function setUp()
{
parent::setUp();
$this->setUpHttpMock();
$this->app->bind(ApiConfigurationInterface::class, FakeApiConfiguration::class);
$this->apiClient = $this->app->make(ApiClient::class);
}
/** #test */
public function example()
{
dd($this->apiClient);
}
}
results
App\ApiClient^ {#355
-apiConfiguration: Tests\FakeApiConfiguration^ {#356}
}
https://laracasts.com/discuss/channels/code-review/laravel-58-interface-binding-while-running-tests?page=1&replyId=581880