I want to mock a custom Validation rule (e.g. App\Rules\SomeRule). But when I run my test, it gives an Mockery\Exception\InvalidCountException: Method...should be called
exactly 1 times but called 0 times.
I've read Laravel's documentation on Mocking, on custom Validation Rules, Service Containers, Service Providers and I cannot figure out why I'm not successfully mocking the rule.
I read this thread but I'm struggling to connect it with my problem which is, "How can I test that my app is using this Rule". Or is that something I cannot test?
Here's my Rule
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class SomeRule implements Rule
{
public function passes($attribute, $value)
{
// some logic, returns TRUE if valid.
}
public function message()
{
//
}
}
My controller
namespace App\Http\Controllers;
use App\Rules\SomeRule;
use Illuminate\Http\Request;
class LoanController extends Controller
{
public function store(Request $request)
{
$request->validate([
'email' => ['required', new SomeRule]
]);
// ...insert in database, return json.
}
}
My Test
namespace Tests\Feature;
use Mockery;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Rules\SomeRule;
class LoansTest extends TestCase
{
use RefreshDatabase;
public function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
/** #test */
public function the_sad_path__when_email_is_invalid()
{
$rule = Mockery::mock(SomeRule::class)->shouldReceive('passes')->once();
$this->app->instance(SomeRule::class, $rule);
$response = $this->json('POST', '/api/loans', ['email' => 'whatevs#gmail.com']);
}
}
I played with the idea of registering SomeRule in the AppServiceProvider. But that still didn't do anything:
public function register()
{
$this->app->bind(SomeRule::class, function ($app) {
return new SomeRule();
});
}
Code in Github
you need to use like this
class LoanController extends Controller
{
public function store(Request $request)
{
$request->validate([
'email' => ['required', new SomeRule()]
]);
// ...insert in database, return json.
}
}
Related
Here is a custom exception
namespace App\Exceptions;
use Exception;
class CustomException extends Exception
{
public function render($request)
{
return response()->view('custom-exception');
}
}
I throw it inside a Request class
class LoginRequest extends FormRequest
{
public function authenticate()
{
if (! Auth::attempt($this->only('email', 'password'))) {
throw CustomException(); //
}
}
}
This is the controller which call the LoginRequest class
class AuthenticatedSessionController extends Controller
{
public function store(LoginRequest $request) //
{
$request->authenticateMember();
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::Home);
}
}
This is the test
use Tests\TestCase;
use App\Models\User;
use App\Exceptions\CustomException;
class EmailVerificationTest extends TestCase
{
public function test_email_verification_screen_can_be_rendered()
{
$user = User::factory()->create([
'email_verified_at' => null,
]);
// $this->expectException(CustomException::class); //this cannot pass
$response = $this->post(
'/login',
[
'email' => 'john#example.com',
'password' => 'secret'
]
);
$response->assertViewIs('custom-exception');
$this->assertInstanceOf(CustomException::class, $response->exception);
}
}
These assertions can pass:
$response->assertViewIs('custom-exception');
$this->assertInstanceOf(CustomException::class, $response->exception);
But this one cannot pass:
$this->expectException(CustomException::class);
Failed asserting that exception of type "App\Exceptions\CustomException" is thrown.
Why? Any idea?
The method expectException() will only work when the exception thrown is not handled.
Please add the below line in your function
$this->withoutExceptionHandling();
then this method expectException() will work.
Trying to implement simple access authorization with Lumen. It works when doing the update (PUT) action.
But I would also like to handle accessing for example all articles.
I also tried the viewAny or view policy method but no success.
Router
$router->group(['prefix' => 'api/v1'], function () use ($router) {
$router->get('articles', ['uses' => 'ArticleController#showAllArticles']);
$router->get('articles/{id}', ['uses' => 'ArticleController#showOneArticle']);
$router->post('articles', ['uses' => 'ArticleController#create']);
$router->delete('articles/{id}', ['uses' => 'ArticleController#delete']);
$router->put('articles/{id}', ['uses' => 'ArticleController#update']);
});
AuthServiceProvider
class AuthServiceProvider extends ServiceProvider
{
public function boot()
{
Gate::policy('App\Article', 'App\Policies\ArticlePolicy');
$this->app['auth']->viaRequest('api', function ($request) {
return app('auth')->setRequest($request)->user();
});
}
}
Policies
namespace App\Policies;
use App\User;
use App\Article;
class ArticlePolicy
{
public function showAllArticles(User $user, Article $post)
{
// not working
return true;
}
public function update(User $user, Article $post)
{
// this works
return true;
}
}
Controller
namespace App\Http\Controllers;
use App\Article;
use Illuminate\Http\Request;
class ArticleController extends Controller
{
public function __construct()
{
$this->middleware('auth:api');
}
public function showAllArticles()
{
$this->authorize('showAllArticles');
return response()->json(Article::all());
}
public function showOneArticle($id)
{
return response()->json(Article::find($id));
}
public function update($id, Request $request)
{
$article = Article::findOrFail($id);
$this->authorize('update', $article);
$article->update($request->all());
return response()->json($article, 200);
}
}
As per the Laravel documentation on Authorization:
"When defining policy methods that will not receive a model instance, such as a create method, it will not receive a model instance. Instead, you should define the method as only expecting the authenticated user:"
public function create(User $user)
So:
public function showAllArticles(User $user)
"As previously discussed, some actions like create may not require a model instance. In these situations, you should pass a class name to the authorize method. The class name will be used to determine which policy to use when authorizing the action:"
$this->authorize('create', Post::class);
So:
$this->authorize('showAllArticles', Article::class);
Laravel 7.x Docs - Authorization - Writing Policies - Methods without Models
Laravel 7.x Docs - Authorization - Authorizing Actions Using Policies - via Controller Helper authorize
No explanation needed.
I need to use my custom validation rule for validating API requests.
Request Class:
This is my request validation rule.
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class LoginRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'username' => 'required',
'password' => 'required'
];
}
public function messages()
{
return [
'username.required' => 'The Username field is required',
'password.required' => 'The Password field is required'
];
}
}
API Controller:
This is my API controller and method.
use Illuminate\Http\Request;
use App\Http\Controllers\API\BaseController as BaseController;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\LoginRequest;
class LoginController extends BaseController
{
public function login(LoginRequest $request)
{
print_r($validatorMsg);
die();
}
}
Unable to get error message.
guys, I resolved the problem this is the solution:
public function login(Request $request)
{
$LoginRequest = New LoginRequest;
$validator = Validator::make($request->all(), $LoginRequest->rules(),$LoginRequest->messages());
if($validator->fails()){
return response()->json($validator->errors(), 422);
}
}
In order to display error messages in your API's, use response() method in your LoginRequest class, so you always return JSON. Something like this:
public function response(array $errors)
{
// Always return JSON.
return response()->json($errors, 422);
}
Now try to submit empty form, and you should be able to view the error message.
I have to make validation of all of Date format only using class/request in Laravel. Can I make validation for all of the requests ? I think i do it in request.php abstract class.
You may try something like this, at first create a BaseController like the following:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class BaseController extends Controller {
public function __construct(Request $request) {
$this->request = $request;
$this->hasValidDate();
}
protected function hasValidDate()
{
if($this->request->method() == 'POST') {
// Adjust the rules as needed
$this->validate($this->request, ['date' => 'required|date']);
}
}
}
Then in your other controllers, extend the BaseController like this example:
namespace App\Http\Controllers\User;
use App\Http\Controllers\BaseController;
class UserController extends BaseController {
public function index()
{
// ...
}
}
Hope you got the idea. Use it wisely.
I am about $this->close() to giving up on Mockery in my unit tests. Here's what's going on, I am working with Laravel 5.1 and I'm trying to test my repository pattern abstraction using Mockery in PHPUnit. I've followed the tutorials, poured over the StackOverflow questions so it's not a duplicate. When you see anything about modules here, it's PingPong Sky Modules package.
Basically, when I try to mock the repository interface and set shouldReceive('create')->with([])->once() , Mockery throws:
Mockery\Exception\InvalidCountException: Method create(array()) from Mockery_0_Modules_Documents_Repositories_DocumentRepositoryInterface should be called exactly 1 times but called 0 times.
DocumentsTest.php
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
class FileUploadTest extends TestCase {
use WithoutMiddleware;
public function mock($class){
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function testFileUpload(){
// Mock the Repository
$mock = $this->mock('\Modules\Documents\Repositories\DocumentRepositoryInterface');
$mock->shouldReceive('create')->with([])->once();
$this->call('POST', '/documents', [], [], []);
$this->assertResponseStatus(201);
}
}
DocumentRepositoryInterface.php
<?php namespace Modules\Documents\Repositories;
interface DocumentRepositoryInterface {
public function create(array $data);
}
DatabaseDocumentRepository.php
<?php namespace Modules\Documents\Repositories;
use Modules\Documents\Repositories\DocumentRepositoryInterface;
use \Illuminate\Database\Eloquent\Model;
class DatabaseDocumentRepository implements DocumentRepositoryInterface {
protected $documents;
public function __construct(Model $documents) {
$this->documents = $documents;
}
public function create(array $data) {
// Eloquent code.
return "response";
}
}
Document.php
<?php namespace Modules\Documents\Entities;
use Illuminate\Database\Eloquent\Model;
class Document extends Model {
protected $fillable = [];
}
routes.php
$this->app->bind(
'Modules\Documents\Repositories\DocumentRepositoryInterface', function(){
return new Modules\Documents\Repositories\DatabaseDocumentRepository(new Modules\Documents\Entities\Document());
});
Route::group(['prefix' => 'documents', 'namespace' => 'Modules\Documents\Http\Controllers'], function(){
Route::post('/', ['as' => '/', 'uses'=> 'DocumentsController#create']);
});
DocumentsController.php
<?php namespace Modules\Documents\Http\Controllers;
use Modules\Documents\Repositories\DocumentRepositoryInterface;
use Pingpong\Modules\Routing\Controller;
use Module;
use Illuminate\Http\Response;
use Illuminate\Http\Request;
class DocumentsController extends Controller {
private $documents;
public function __construct(DocumentRepositoryInterface $doc){
$this->documents = $doc;
}
public function create(Request $request){
$this->documents->create([]);
return response("", Response::HTTP_CREATED);
}
}
I want to say it has something to do with the mocked object not getting injected into the DocumentsController because the create() function is getting called. I put a print_r in the create function and it displayed in my console. This is strange and it could also be related to PingPong Sky Modules. What am I doing wrong or not doing?