Laravel phpunit test maatwebsite/excel import - laravel

I've created an maatwebsite/excel import routine and i would like to test it.
The maatwebsite/excel testing page does not provide me with any other information than to fake it.
But i need to upload my real excel-file, as i want to validate whether the data from the excel file was processed correctly.
Here's my upload input field and the corresponding button to hit the endpoint /import
<form action="/import" method="post" enctype="multipart/form-data">
#csrf
<div class="form-group">
<input type="file" class="form-control-file file-path" name="fileToUpload">
</div>
<button type="submit" class="btn btn-primary">Import File</button>
</form>
on the controller side of view, the uploaded file will be processed and imported.
...
public function store(Request $request) {
$request->validate([
'fileToUpload' => 'required|file|max:4096|mimes:xls,xlsx',
]);
// start the import
Excel::import(new SheetNavigator, request()->file('fileToUpload'));
...
The file that needs to be imported is located within my testing environment under:
/tests
/files
/myexcel.xlsx
public function test_user_can_import_file() {
Excel::fake();
$datafile = new UploadedFile(
base_path('tests/files/myfile.xlsx'),
'myfile.xlsx',
'xlsx',
13071,
true);
$res = $this->post('/import', [
'fileToUpload' => $datafile
]);
// asserting, that everything works..
}
I need a test to verify that the upload was successful and that the import-routine was triggered.
I tried everything, from faking anything to using storages.
I appreciate any kind of help, thank you!
Chris

Generally speaking the ideal way to do this is by mocking the Excel class and then checking if import has been called with the given file. Despite the fact that this seems to be a static call it's actually a Facade call so you can swap it out with a mock. In fact it seems to offer this functionality on its own:
public function testThatItImportsTheUploadedFile() {
$file = UploadedFile::fake()->create('myexcel.xlsx');
Excel::fake();
$this->post('/import', [
'fileToUpload' => $file
]);
Excel::assertImported('myexcel.xlsx');
}
Note: This verifies that the endpoint works as expected given that the file is uploaded and Excel::import will work as expected.
If you want to test with your real file you can possibly create a new UploadedFile instance (linking the symfony base class since that's where the constructor is).
$file = new UploadedFile(
base_path('tests/files/myfile.xlsx'),
'myfile.xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
null,
true
);

Related

Laravel Livewire File Upload Not Validating and is returning a Livewire\TemporaryUploadedFile instance

So I have a Laravel app and for some reason Livewire isn't handling file uploads as per the documentation. This is causing me to be unable to save or validate the file in terms of size and/or mimetype.
I have copied the example provided in the documentation (https://laravel-livewire.com/docs/2.x/file-uploads) exactly (literally copy and pasted from the docs) and when I upload a file, I always get a Livewire/TemporaryUploadedFile instance which won't let me validate. In my actual app I need to restrict the filesize of uploaded files as well as the type (.wav, .ogg, .mp4)
For clarity, I'll copy the file contents from my files - but as I've said, I copy and pasted from the example given:
-- upload-photo.blade.php
<input type="file" wire:model="photo">
#error('photo') <span class="error">{{ $message }}</span> #enderror
<button type="submit">Save Photo</button>
</form>
-- UploadPhoto.php
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
class UploadPhoto extends Component
{
use WithFileUploads;
public $photo;
public function updatedPhoto()
{
$this->validate([
'photo' => 'image|max:1024', // 1MB Max
]);
}
public function save()
{
dd($this->photo);
}
public function render()
{
return view('livewire.upload-photo');
}
}
I've also done a quick screencast to show how the realtime validation is failing to detect the file type: https://www.dropbox.com/s/5981v5gncdcwc7d/2022-03-23_21-25-06.mp4?dl=0
Any help would be appreaciated.
Thanks
Anthony
That looks about right.
You can now save the TemporaryUploadedFile to the 'photos_directory'
public function save()
{
#dd($this->photo);
$this->photo->store('photos_directory');
}
as per the documantation https://laravel-livewire.com/docs/2.x/file-uploads

The PUT method is not supported for this route

I cannot submit form information through to store function in laravel controller. The form needs to create a - new - profile for a registered user.
I have even recreated the project, and redone the form - moving back into plain html as I suspect that the laravelCollective functions may be causing it but still the same error.
I have even rearranged the the form attributes as suggested in another post/thread.
I have even recreated the project, and redone the form - moving back into plain html as I suspect that the laravelCollective functions may be causing it but still the same error.
I have even rearranged the the form attributes as suggested in another post/thread.
The Form:
< form method="POST" enctype="multipart/form-data" action="{{ url('users/profile') }}" accept-charset="UTF-8" >
#csrf
...
// input fields here
...
< /form >
The Routes:
Route::resource('users/profile', 'ProfileController');
Route::get('/home', 'HomeController#index')->name('home');
Route::resource('users', 'UserController');
Route::post('users/profile', 'ProfileController#store')->name('profile.store');
The ProfileController#store function:
//some code omitted
public function store(Request $request)
{
$this->validate($request, [
'firstname'=>'required',
'lastname'=>'required',
...
'desc'=>'required'
]);
//handle file upload
if($request->hasFile('cover_image')) {
//Get file name with extension
$fileNameWithExt = $request->file('cover_image')->getClientOriginalName();
//Just file name
$fileName = pathinfo($fileNameWithExt, PATHINFO_FILENAME);
//Just Ext
$ext = $request->file('cover_image')->getClientOriginalExtension();
//FileName to Store
$fileNameToStore = $fileName.'_'.time().'_'.$ext;
//upload image
$path = $request->file('cover_image')->storeAs('public/users/'.auth()->user()->id.'cover_images/'.$request->input('firstname').'_'.$request->input('lastname').'_'.auth()->user()->id.'/',$fileNameToStore);
} else {
$fileNameToStore = 'noimage.jpg';
}
/*
*/
$profile = new Profile;
$profile->firstname = $request->input('firstname');
$profile->lastname = $request->input('lastname');
...
$profile->desc = $request->input('desc');
$profile->save();
return redirect('/users/profile');//->with('success','Profile Created');
}
The famous error:
Symfony \ Component \ HttpKernel \ Exception \
MethodNotAllowedHttpException The PUT method is not supported for this
route. Supported methods: GET, HEAD, POST.
Not sure what is causing the error, help appreciated.
If I understand it correctly this is for store function right? then you don't have to put #method('PUT') inside your form it should POST. The route of store in resource is POST.
this is your code that i deleted the #method('PUT')
< form method="POST" enctype="multipart/form-data" action="{{ url('users/profile') }}" accept-charset="UTF-8" >
#csrf ...
// input fields here ...
< /form >
The Routes: Route::resource('users/profile', 'ProfileController');
Route::get('/home', 'HomeController#index')->name('home');
Route::resource('users', 'UserController'); Route::post('users/profile', 'ProfileController#store')->name('profile.store');
and the PUT method is used for updating. When update in controller you need to pass id in your form that should look like this.
< form method="POST" enctype="multipart/form-data" action="{{ url('users/profile', $data->id) }}" accept-charset="UTF-8" >
#method('PUT')
#csrf ...
// input fields here ...
< /form >
I hope it helps!
you have problem in your routes file simply change your edit route to this route
Route::match(['put', 'patch'], 'the path you want /{id}','controllername#functionname');
you should notice that if you are new to laravel you should pass the id to this route as shown in this part {id} so that your edit function could display the previous data of it and also if you want to submit a the form it should have the put method and the html basic forms doesn't support that so you should find a way to submit it like using laravel collective or maybe put a hidden method in your form
if it doesn't work please give me a call
When using the Laravel resource method on a route, it makes things pretty specific in terms of what it is expecting. If you take a look at the chart on the manual, it is looking for a uri with the updating element id returned as part of the uri. The example looks like: /photos/{photo}. I'm not sure that this is how you've structured your html form.
You said you were using the LaravelCollective to get this working. This usually works fine, and has the massive advantage of easy form-model binding. But it helps to include the named route, which includes 'update' for the update resource. For example:
{!! Form::model($yourModel, array('route' => array('yourRoute.update',
$yourModel->id), 'method'=>'PUT', 'id'=>'Form'))!!}
I have not had an issue with the Collective using this method.

laravel-5.7: data is not saving into database, Object not found

I'm trying to save data into db but its not saving and says that object not found, can anyone suggest me solution, i am following this tutorial: https://laracasts.com/series/laravel-from-scratch-2018/episodes/10
controller:
public function index()
{
$projects = Project::all();
return view('projects.index', compact('projects'));
}
public function create()
{
return view('projects.create');
}
public function store()
{
$project = new Project();
$project->title = request('title');
$project->description = request('description');
$project->save();
return redirect('/projects');
}
routes:
Route::get('/projects','ProjectsController#index');
Route::post('/projects','ProjectsController#store');
Route::get('/projects/create','ProjectsController#create');
create.blade.php:
<form method="POST" action="/projects">
{{ csrf_field() }}
<div>
<input type="text" name="title" placeholder="Project title">
</div>
<div>
<textarea name="description" placeholder="Project description"></textarea>
</div>
<div>
<button type="submit">Create Project</button>
</div>
</form>
index.blade.php:
#foreach($projects as $project)
<li>{{ $project->title }}</li>
#endforeach
You have missed out passing request parameter in the controller store()
public function store(Request $request)
{
$project = new Project();
$project->title = $request->title;
$project->description = $request->description;
$project->save();
return redirect('/projects');
}
And also don't forget to include use Illuminate\Http\Request; above(outside) controller class.
The Laravel code you've posted is correct under a properly configured website. The error from your comments:
Object not found! The requested URL was not found on this server. The
link on the referring page seems to be wrong or outdated. Please
inform the author of that page about the error. If you think this is a
server error, please contact the webmaster. Error 404 localhost
Apache/2.4.33 (Win32) OpenSSL/1.1.0h PHP/7.2.7
is an Apache error page, which means it's not requesting a page from your laravel project at all. The data is probably saving in your database, but then you redirect away to a page that is outside your project, and Apache can't find it.
Your website is located at http://localhost/laravel/public, which means you need to access the projects page at http://localhost/laravel/public/projects. However, redirect('/projects') gives you an absolute path instead of a relative path, sending you to http://localhost/projects, which does not exist.
Solutions
Since this is a local development project, I'm going to skip the issues with the improper Apache configuration and focus on other ways to avoid the error.
Option 1
Use a named route:
Route::get('/projects','ProjectsController#index')->name('projects.index');
and use the name of the route for the redirect:
return redirect()->route('projects.index');
This should generate correct urls within your project.
Option 2
Use serve for development instead of Apache.
Open a terminal in your Laravel project directory and run this command:
php artisan serve
This will start PHP's built-in webserver at http://localhost:8000, skipping Apache entirely. During development this is perfectly fine.

How to pass params from laravel to ReactJS?

My application uses Lavarel and I have certain ReactJS components. Routing is done by Laravel. For a route /Users/<userid>, if the controller is UserController.php and view is userview.blade.php, how do I get the <userid> from the URL in my react component loaded in a div on userview.blade.php?
You can try out this library from Laracasts which allows you to pass server-side data (string/array/collection) to your JavaScript via a Facade in your Controller.
This may look something like this (haven't tested it, but you get the gist).
public function UserController($userid)
{
JavaScript::put([
'userid' => $userid,
]);
return View::make('yourview');
}
The docs further show how to access it from your view.
https://github.com/laracasts/PHP-Vars-To-Js-Transformer
UserController.php
you can access the userid and send it to view like this:
public function getUser($userid) {
return view('userview')->with('userid', $userid);
}
userview.blade.php
you can grab it (in some input) like this:
<input type="text" value="{{ $userid }}" />
This is, how I understand your problem. If it is not so, kindly correct me!!!

Model validation on different request

I have several models all of which have a create page. When a model is created, I do not perform any validation. This is because I allow the user at any time to go back and add to things.
However, at some point, I provide a button to the user
<a href="{{ route('projects.push', $project->id) }}" class="btn btn-info pull-right" data-token="{{ csrf_token() }}">
Push
</a>
All of the models in question are related to the Project model. When they click the push button, I am going to send the Models to an external system. However, at this point I need to validate that the
models being sent have all the required data. I know about validation on a model, but this is when they are created. Is it possible to validate them on a completely different action?
Thanks
Of course it is possible. It would be smart to store your rules and/or messages inside you model as a static function. An example would be:
// Project model
public static function rules()
{
return [
'field1' => 'rules1..',
'field2' => 'rules2..'
];
}
Then you can retrieve your rules anywhere in your application:
Validator::make($fields, Project::rules());
One last thing. You said you validate your model when it already has been created. I don't know if putting the entire retrieved model variable instead of $fields will work. Example:
$project = Project::find($id);
// Try this
Validator::make($project, Model::rules());
// Otherwise try this
Validator::make($project->attributes, Model::rules());
Hope this helps :)

Resources