testing php laravel controller shouldReceive arguments - php

I have a laravel model which uses ardent/eloquent. I am trying to set up tests for the controller in particular, storing a new model that uses the ardent model.
The method works in the app but I'm having trouble with my tests
I'm having problems working out how to mock the calls this method makes.
My controllers set up and the method in question is this one:
use golfmanager\service\creator\TicketCreatorInterface;
//controller manages the ticket books
class BooksController extends BaseController {
/**
* Book Repository
*
* #var Book
*/
protected $book;
protected $ticket;
public function __construct(Book $book, TicketCreatorInterface $ticket)
{
$this->book = $book;
$this->ticket = $ticket;
}
public function store()
{
$input = Input::all();
$result = $this->book->save();
if ($result) {
//if book created then create tickets
$this->ticket->createTicket($input, $this->book);
return Redirect::route('books.index');
}
return Redirect::route('books.create')
->withInput()
->withArdentErrors()
->with('message', 'There were validation errors.');
}
And the methods used by the interface (TicketCreator):
public function createTicket($input, $book) {
//dd($input);
$counter = $input['start_number'];
while($counter <= $input['end_number']) {
$ticketDetails = array(
'ticketnumber'=>$counter,
'status'=>'unused',
'active'=>1
);
$this->ticket->create($ticketDetails)->save();
$this->ticket->book()->associate($book)->save();
$counter = $counter+1;
}
return $counter;
}
My test is as follows:
use Mockery as m;
use Way\Tests\Factory;
class BooksTest extends TestCase {
public function __construct()
{
$this->mock = m::mock('Ardent', 'Book');
$this->collection = m::mock('Illuminate\Database\Eloquent\Collection')->shouldDeferMissing();
}
public function setUp()
{
parent::setUp();
$this->attributes = Factory::book(['id' => 1, 'assigned_date'=> '20/11/2013']);
$this->app->instance('Book', $this->mock);
}
public function testStore()
{
Input::replace($input = ['start_number'=>1000, 'end_number'=>1010, 'assigned_date'=>'20/11/2013']);
$this->mock->shouldReceive('save')->once()->andReturn(true);
$this->ticket->shouldReceive('createTicket')->once()->with($input, $this->mock)->andReturn(true);
//with($input);
//$this->validate(true);
$this->call('POST', 'books');
$this->assertRedirectedToRoute('books.index');
}
Currently I get an error:
No matching handler found for Book::save()
Is this being thrown because the book model doesnt contain a save method? If it is how do I mock the model correctly. I don't want it to touch the database (although it could if it has to).
Is it the multiple saves in the createTicket method?
Still learning how to set up tests correctly - slowly getting there but not enough knowledge yet.
If I change the name of the method in shouldReceive to say 'store' it still comes up with the save() error message.
Update:
I have isolated part of the problem to the createTicket call. I've changed my testStore test and updated as above.
My error with this current test is: Undefined index: start_number.
If I remove the call to createTicket in the controller method I don't get an error. I tried using Input::replace to replace the input from a form but appears not getting through to my function
How can I simulate a form input in the mocked objects?
Thanks

Related

Laravel Validator Not Returning Key

I am creating a new API call for our project.
We have a table with different locales. Ex:
ID Code
1 fr_CA
2 en_CA
However, when we are calling the API to create Invoices, we do not want to send the id but the code.
Here's a sample of the object we are sending:
{
"locale_code": "fr_CA",
"billing_first_name": "David",
"billing_last_name": "Etc"
}
In our controller, we are modifying the locale_code to locale_id using a function with an extension of FormRequest:
// This function is our method in the controller
public function createInvoice(InvoiceCreateRequest $request)
{
$validated = $request->convertLocaleCodeToLocaleId()->validated();
}
// this function is part of ApiRequest which extend FormRequest
// InvoiceCreateRequest extend ApiRequest
// So it goes FormRequest -> ApiRequest -> InvoiceCreateRequest
public function convertLocaleCodeToLocaleId()
{
if(!$this->has('locale_code'))
return $this;
$localeCode = $this->input('locale_code');
if(empty($localeCode))
return $this['locale_id'] = NULL;
$locale = Locale::where(Locale::REFERENCE_COLUMN, $localeCode)->firstOrFail();
$this['locale_id'] = $locale['locale_id'];
return $this;
}
If we do a dump of $this->input('locale_id') inside the function, it return the proper ID (1). However, when it goes through validated();, it doesn't return locale_id even if it's part of the rules:
public function rules()
{
return [
'locale_id' => 'sometimes'
];
}
I also tried the function merge, add, set, etc and nothing work.
Any ideas?
The FormRequest will run before it ever gets to the controller. So trying to do this in the controller is not going to work.
The way you can do this is to use the prepareForValidation() method in the FormRequest class.
// InvoiceCreateRequest
protected function prepareForValidation()
{
// logic here
$this->merge([
'locale_id' => $localeId,
]);
}

How to construct an object that depends on the session to be loaded

Let's suppose we have a site that shows a random list of 20 movies. Logged in users, however, can select their favorite movies, so those movies will be shown instead. This list of movies is shown both in the home page and in some other pages.
To follow the DRY principle, we could encapsulate this logic in its own class, and then inject this class wherever it is necessary to show the list of movies. This class will also have other methods that will be used throughout the application. For example, there is also a method to get one random movie.
The class could look like this (please note this is a simplified example):
class MovieService
{
/** #var Collection $movies */
protected $movies;
public function __construct()
{
$this->movies = Auth::check() ? Auth::user()->favoriteMovies : $this->randomMovies();
}
public function getRandomMovies(): Collection
{
return $this->movies->random(20);
}
public function getOneRandom(): Movie {
return $this->movies->random();
}
protected function randomMovies() {
return Movie::inRandomOrder()->take(20)->get();
}
}
Note: Please note that this is an example and that some things could be improved.
As this class could be used multiple times in the same request, it is a good idea to make it a singleton in the IoC container, so that the queries that are run when instantiated are not run more than once.
However, now we encounter a problem. We need this class in a private method in a controller. We could directly call the app container like app() or App::make() but we would like to avoid facades and global helpers with custom dependencies.
class HomeController extends Controller
{
/** #var MovieService $movieService */
protected $movieService;
public function __construct(MovieService $movieService)
{
$this->movieService = $movieService;
}
public function index()
{
$movies = $this->getMovies();
return view('home', compact('movies'));
}
protected function getMovies()
{
// Let's imagine there's some extra logic here so that we would actually need this method.
return $this->movieService->getRandomMovies();
}
}
We have found a problem. A controller's constructor is run before the middleware pipeline, which means that there's no session and, hence, no user identification. Now Auth::check() in MovieService is always returning false, so the default movies will always be shown.
What would you do to fix this?
It's cleaner to not use the constructor of an object for logic, only for managing dependencies. Coincidentally this will also fix the issue you're having by moving the Auth::check() logic to your getter methods instead. Besides that you could also consider injecting the AuthManager instead of relying on the Auth facade, but that's just a sidenote.
class MovieService
{
/** #var AuthManager $auth */
protected $auth;
protected $movies;
public function __construct(Illuminate\Auth\AuthManager $auth)
{
$this->auth = $auth;
}
public function getRandomMovies(): Collection
{
return $this->getMoviesForCurrentUser()->random(20);
}
public function getOneRandom(): Movie {
return $this->getMoviesForCurrentUser()->random();
}
protected function randomMovies() {
if ($this->movies === null) {
$this->movies = Movie::inRandomOrder()->take(20)->get();
}
return $this->movies;
}
protected function getMoviesForCurrentUser() {
if ($this->auth->check()) {
return $this->auth->user->favoriteMovies;
}
return $this->randomMovies();
}
}

Laravel attach returns undefined method for belongsToMany relationship

I would like to create a question which has many surveys. In the questions Model:
public function surveys()
{
return $this->belongsToMany(Survey::class, 'survey__surveyquestions');
}
And in the controller when saving a new question:
private $questions;
public function __construct(QuestionsRepository $questions)
{
parent::__construct();
$this->questions = $questions;
}
public function store(Request $request)
{
$this->questions->create($request->all());
$this->questions->surveys()->attach($request->surveys);
return redirect()->route('admin.survey.questions.index')
->withSuccess(trans('core::core.messages.resource created', ['name' => trans('survey::questions.title.questions')]));
}
But I get the following error when it gets to the attach line:
(1/1) FatalErrorException Call to undefined method
Modules\Survey\Repositories\Eloquent\EloquentQuestionsRepository::surveys()
I notice the error mentions EloquentQuestionsRepository but I have added no methods in there so it's just an empty class:
class EloquentQuestionsRepository extends EloquentBaseRepository implements QuestionsRepository
{
}
QuestionRepository:
interface QuestionsRepository extends BaseRepository
{
}
As explained in the response to the main post - the constructor resolves the QuestionsRepository to instance of EloquentQuestionsRepository, which by the look of it is not what the store method needs.
What I would probably do is to make call to create method directly on the model and remove constructor all together - that is unless you need the instance of QuestionsRepository anywhere else in your controller:
public function store(Request $request)
{
$question = Question::create($request->all());
$question->surveys()->attach($request->surveys);
...
}
Also - I'm not sure passing $request->all() is the best thing to do - I'd probably use $request->only(...) or $request->all(...) specifying which items you want to get from the request rather than passing everything from the request to the create method.
On the other note - you could also use Form Request, which would validate data for your before passing it to the store method.
https://laravel.com/docs/5.5/validation#form-request-validation

Laravel: Avoid to create instance of a model with a constructor in the controller

I'm following a course for Laravel 4 and the teacher did a code refactoring and introduced a magic method constructor in the controller
class UtentiController extends BaseController {
protected $utente;
public function __construct(Utenti $obj) {
$this->utente = $obj;
}
public function index() {
$utenti = $this->utente->all();
return View::make('utenti.index', ["utenti" => $utenti]);
}
public function show($username) {
$utenti = $this->utente->whereusername($username)->first(); //select * from utenti where username = *;
return View::make('utenti.singolo', ["utenti" => $utenti]);
}
public function create() {
return View::make('utenti.create');
}
public function store() {
if (! $this->utente->Valido( $input = Input::all() ) ) {
return Redirect::back()->withInput()->withErrors($this->utente->messaggio);
}
$this->utente->save();
return Redirect::route('utenti.index');
}
}
Thanks to this code I don't have to create a new instance of the Utenti model every time:
protected $utente;
public function __construct(Utenti $obj) {
$this->utente = $obj;
}
Now I can access the database with this simple approach:
$this->utente->all();
Whereas before, I had to do this:
$utente = new Utente;
$utente::all();
Does this type of technique have a name? (is it a pattern?).
My understanding is that every time the controller is invoked it automatically generates an instance of the User class (model) and applies an alias (reference) attribute $utente
Is that correct?
Also, here is the code for the Utenti model:
class Utenti extends Eloquent {
public static $regole = [
"utente" => "required",
"password" => "required"
];
public $messaggio;
public $timestamps = false;
protected $fillable = ['username','password'];
protected $table = "utenti";
public function Valido($data) {
$validazione = Validator::make($data,static::$regole);
if ($validazione->passes()) return true;
$this->messaggio = $validazione->messages();
return false;
}
}
This is called dependency injection or short DI. When creating a new instance of the Controller, Laravel checks the constructor for type hinted parameters (The ones that have a type defined like __construct(Utenti $obj){) If your controller has any of these Laravel tries to create an instance of the class and injects it into the constructor.
The reason why this is done is that it's becoming very clear what the dependencies of a class (in this case your controller) are. It gets especially interesting if you type hint an Interface instead of a concrete class. You then have to tell Laravel with a binding which implementation of the interface it should inject but you can also easily swap an implementation or mock it for unit testing.
Here are a few links where you can get more information:
Laravel docs IoC container
Method dependency injection in Laravel 5
StackOverflow - What is Inversion of Control?

How create return values when mocking Eloquent on Laravel

I am working on a project on Laravel, and I have developed my own version of User. I know that Laravel comes with it is own implementation, but just for the sake of the argument, let's forget that.
I am trying to learn how to test in Laravel, and I encountered the following problem:
Part of the controller
<?php
class UsersController extends BaseController {
protected $user;
public function __construct(User $user) {
$this->user = $user;
}
public function login() {
// Input, Validation, blah, blah blah
....
// Find user
$users = $this->user->where('email', '=', $email);
if ($users->count() == 0) {
// Do something
} else {
// Do something else different
}
// Lot more stuff...
}
}
Now comes testing. As you see I put the dependency of Eloquent through the constructor so I can mock it. The test are as follow:
<?php
class UsertTest extends TestCase {
public function __construct() {
$this->userMock = Mockery::mock('Eloquent', 'User');
}
public function tearDown() {
Mockery::close();
}
public function testLogin() {
// Unimportant
$data = ...
$this->userMock
->shouldReceive('where')
->once()
->andReturn('foo');
$this->app->instance('User', $this->userMock);
// Send the data
$this->post('/login', $data);
When I run the tests, I get this error
PHP Fatal error: Call to a member function count() on a non-object
So it seems that the mock is working, in the sense that the Database is not being hit, but now I am with the problem that I need to mock as well the value returned by the call.
How should I go about it?

Categories