CakePHP: calling testAction to a json-returning method causes missing view exception - unit-testing

What am I missing here? Here is my controller code:
public function calculate() {
$this->set(array(
"route" => array("A" => 1, "B" => 2),
"_serialize" => array("route")
));
return;
}
Here is a line from my routes.php file:
Router::parseExtensions();
Here is my test code:
$result = $this->testAction("/itinerary/calculate.json", array(
"method" => "POST",
"return" => "contents"
));
This code throws
MissingViewException: View file "C:\xampp\htdocs\fiver\app\View\Itinerary\calculate.ctp" is missing.
I am obviously missing something here. Please help. Another test for another controller with JSON works just fine

Got it. CakePHP requires the RequestHandler component to be explicitly added to the controller for the extensions to work. I've added this line, it started to work
public $components = array('RequestHandler');

If you dont have/want a view for your controller you can simply add
$this->autoRender = false;
// EDIT: Only working if you dont want an output but thats not the case

Related

Using Ember.set to change a value, getting error that I have to use Ember.set

In my application, I have to update a record (eventToUpdate) with the data from another object (updatedEvent). To do this, I use the following code:
editEvent (updatedEvent, eventToUpdate) {
eventToUpdate.set('name', updatedEvent.name);
eventToUpdate.set('matching', updatedEvent.matching);
eventToUpdate.set('dcfEvent', updatedEvent.dcfEvent);
eventToUpdate.save().then(() => {
toastr.success('Event updated');
}).catch((error) => {
toastr.error('There occured an error while trying to update the event');
console.log(error);
});
},
When I try to update the event, I get the following error:
Assertion Failed: You must use Ember.set() to set the `name` property (of [object Object]) to `DCF tests`."
I have also tried setting the values with Ember.set, like this:
Ember.set(eventToUpdate, 'name', updatedEvent.name);
But that gives the same result..
I use Ember.js 1.13
It seems that eventToUpdate is not an Ember Object and someone is watching this property. So use Ember.set to set values:
editEvent (updatedEvent, eventToUpdate) {
Ember.set(eventToUpdate, 'name', updatedEvent.name);
Ember.set(eventToUpdate, 'matching', updatedEvent.matching);
Ember.set(eventToUpdate, 'dcfEvent', updatedEvent.dcfEvent);
eventToUpdate.save().then(() => {
toastr.success('Event updated');
}).catch((error) => {
toastr.error('There occured an error while trying to update the event');
console.log(error);
});
},
The problem lied in the structure of my data. I was trying to edit sub events, which are part of a root event. The problem was solved by first loading the single subevent from the backend and then editing that single subevent. It looks somewhat like this:
var event = this.store.findRecord('event', subevent.id);
event.set('name', updatedEvent.name);
event.set('matching', updatedEvent.matching);
event.save().then(() => {
toastr.success('element successfully updated');
}).catch((error) => {
console.log(error);
});

Angular2 Component: Testing form input value change

I have a text input and i'm listening for the changes.
mycomponent.ts
ngOnInit() {
this.searchInput = new Control();
this.searchInput.valueChanges
.distinctUntilChanged()
.subscribe(newValue => this.search(newValue))
}
search(query) {
// do something to search
}
mycomponent.html
<search-box>
<input type="text" [ngFormControl]="searchInput" >
</search-box>
Running the application everything works fine, but i want to unit-test it.
So here's what i tried
mycomponent.spec.ts
beforeEach(done => {
createComponent().then(fix => {
cmpFixture = fix
mockResponse()
instance = cmpFixture.componentInstance
cmpFixture.detectChanges();
done();
})
})
describe('on searching on the list', () => {
let compiled, input
beforeEach(() => {
cmpFixture.detectChanges();
compiled = cmpFixture.debugElement.nativeElement;
spyOn(instance, 'search').and.callThrough()
input = compiled.querySelector('search-box > input')
input.value = 'fake-search-query'
cmpFixture.detectChanges();
})
it('should call the .search() method', () => {
expect(instance.search).toHaveBeenCalled()
})
})
Test fails as the .search() method is not called.
I guess i have to set the value in another way to have the test realize of the change but i really don't know how.
Anyone has ideas?
It might be a little bit late, but it seems that your code is not dispatching input event after setting input element value:
// ...
input.value = 'fake-search-query';
input.dispatchEvent(new Event('input'));
cmpFixture.detectChanges();
// ...
Updating input html field from within an Angular 2 test
Triggering the value change of FormControl is as simple as:
cmpFixture.debugElement.componentInstance.searchInput.setValue(newValue);
Custom component with #input, subscriptions, two way data binding
If you got a custom component you would need further changes in your application to be able to successfully unit test your application
have a look at the gist here this will give you some idea
https://gist.github.com/AikoPath/050ad0ffb91d628d4b10ef81736af386/raw/846c7bcfc54be8cce78eba8d12015bf749b91eee/#ViewChild(ComponentUnderTestComponent).js
More over complete reading over here carefully otherwise you can easily get confused again -
https://betterprogramming.pub/testing-angular-components-with-input-3bd6c07cfaf6

Angular 2 unit test

Got a couple of problems with my unit testing with Jasmine. First one:
I need to test this in a Component called CaseList:
gotoDetail(case: Case){
this._router.navigate(['CaseDetail', {"id": case.id}]);
}
All my attempts at the tests give the error is this._router is undefined, well that's because I haven't defined it in my test, as I can't figure out how! I haven't even come up with any good attempts at the tests, as I have no idea how to proceed. So that's why I haven't posted any attempt here...
Edit: The part in the router-test which is related to to the problem above, but I test all the routing in a separate file! This test works!
it('Should navigate to Case Detail List', (done) => {
router.navigate(['CaseDetail', {id: 'test'}]).then(() => {
expect(location.path()).toEqual('/casedetail/test');
done();
}).catch(e => done.fail(e));
});
Second tests from a Detail Component (where user is navigated after choosing case) :
addStep(){
this.case.getSteps().push(new Step());
}
I also have a remove method I need to test:
removeStep(step: Step){
this.case.removeStep(step);
}
Constructor for this component:
constructor(public _routeParams: RouteParams, public _service: Service) {
this.case = _service.getById(_routeParams.get('id'));
}
So the test I tried doing for the add-method:
it('passes new step to case-class', () => {
spyOn(case, 'addStep')
.and.returnValue(Observable.of({complete: true}))
caseDetail.addStep();
expect(case.addStep).toHaveBeenCalledWith(step);
});
So these methods call the methods that are in a separate class called "Case".
The error I'm getting when testing these are that case is null. I guess the routing and service messes it up, as in the same Component I have a other "identical" methods, and testing those works fine. But they belong to a different class.
Method in same component, referring to a "Step"-class:
addFeedback(step: Step){
step.addFeedback(new Feedback());
}
The testing works perfectly:
it('passes feedback value to Step class', () => {
spyOn(step, 'addFeedback')
.and.returnValue(Observable.of({complete: true}))
caseDetail.addFeedback(step);
expect(step.addFeedback).toHaveBeenCalledWith(feedback);
})
So obviously in testing the component I should have everything defined that is needed, since the testing of the feedback method works. I just need to define the "case" object somehow, so that it doesn't complain about it being null.
Well hopefully you get my problem at hand and hopefully you can help! :)
For your test cases to work, you will have to add router and case as a provider before the test.
Routing Example:
import {RootRouter} from 'angular2/src/router/router';
import {Location, RouteParams, Router, RouteRegistry, ROUTER_PRIMARY_COMPONENT} from 'angular2/router';
import {SpyLocation} from 'angular2/src/mock/location_mock';
import {provide} from 'angular2/core';
describe('Router', () => {
let location, router;
beforeEachProviders(() => [
RouteRegistry,
provide(Location, {useClass: SpyLocation}),
provide(Router, {useClass: RootRouter}),
provide(ROUTER_PRIMARY_COMPONENT, {useValue: App}),
]);
beforeEach(inject([Router, Location], (_router, _location) => {
router = _router;
location = _location;
}));
it('Should be able to navigate to Home', done => {
router.navigate(['Index']).then(() => {
expect(location.path()).toBe('');
done();
}).catch(e => done.fail(e));
});
});
Case Provider:
import {Case} from '../case';
beforeEachProviders(() => [
provide(case, Case)
]);

Calling named routes in laravel tests

consider the following:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class HomeRouteTest extends TestCase
{
public function testVisitTheHomePage()
{
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
public function testVisitTheAboutPage()
{
$response = $this->call('GET', '/about');
$this->assertEquals(200, $response->status());
}
}
Is there away, not that I have seen documented >.>, to do something like:
$response = $this->call('GET', 'home.about');
$this->assertEquals(200, $response->status());
Or .... Is that how you do it?
The error I get is:
vagrant#scotchbox:/var/www$ phpunit
PHPUnit 4.8.21 by Sebastian Bergmann and contributors.
FF
Time: 3.41 seconds, Memory: 14.25Mb
There were 2 failures:
1) HomeRouteTest::testVisitTheHomePage
Failed asserting that 404 matches expected 200.
/var/www/tests/HomeRouteTest.php:12
2) HomeRouteTest::testVisitTheAboutPage
Failed asserting that 404 matches expected 200.
/var/www/tests/HomeRouteTest.php:19
FAILURES!
Tests: 2, Assertions: 2, Failures: 2.
This solution works for any Laravel 5 version to my knowledge. Especially Laravel 5.4+ unlike the other solution mentioned here.
If your named route has parameters, you can just do this:
$response = $this->get(route('users.show', [
'user' => 3,
]));
$response->assertStatus(200);
If your named route has no parameters then you can just do this:
$response = $this->get(route('users.index'));
$response->assertStatus(200);
Nice and simple.
This is a very late response, but I think what you're looking for is:
$this->route('GET', 'home.about');
$this->assertResponseOk(); // Checks that response status was 200
For routes with parameters, you can do this:
$this->route('GET', 'users.show', ['id' => 3]); // (goes to '/users/3')
I needed this to test some ghastly routes I'm working with, which look like this:
Route::resource(
'/accounts/{account_id}/users',
'AccountsController#users',
[
'parameters' => ['account_id'],
'names' => [
'create' => 'users.create',
'destroy' => 'users.destroy',
'edit' => 'users.edit',
'show ' => 'users.show',
'store' => 'users.store',
'update' => 'users.update',
]
]
);
To make sure that my routes and redirects went to the right page I did this:
/**
* #test
*/
public function users_index_route_loads_correct_page()
{
$account = Account::first();
$response = $this->route('get', 'users.index', ['account_id' => $account->id]);
$this->assertResponseOk()
->seePageIs('/accounts/' . $account->id . '/users');
}
To make sure I was using the right view file (we all make copy-paste errors, right?), I did this:
/**
* #test
*/
public function users_index_route_uses_expected_view()
{
$account = Account::first();
$response = $this->route('get', 'users.index', ['account_id' => $account->id]);
$view = $response->original; // returns View instance
$view_name = $view->getName();
$this->assertEquals($view_name, 'users.index');
}
If it weren't for the Structure feature in PHPStorm and the documentation from Laravel 4.2 that I stumbled over, I don't think I would have ever figured out how to test those routes. I hope this saves someone else a bit of time.
To be honest I don't see any reason to test names route. Named routes are rather for easier use inside application and you should rather test concrete url and not names route to make sure you are using correct url.
If you need to really test named route, I would create helper function for that with simple array:
protected function getNamedRoute($name)
{
$routes = [
'home.about' => '/about',
];
return $routes[$name];
}
and then in your test:
public function testVisitTheHomePage()
{
$response = $this->call('GET', $this->getNamedRoute('home.about'));
$this->assertEquals(200, $response->status());
}
The second thing is that you can make those tests a bit cleaner, you don't need to use:
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
you can use:
$this->visit('/')->seeStatusCode(200);
For me it's much more legible and you can write less code to achieve same.

Username in URL using Regex & Route

I have a method in my Users Controller called view, which should display a specified (by URL) user:
public function view($username = null) {
$this->User->username = $username;
if (!$this->User->exists()) {
throw new NotFoundException('Няма такъв потребител!');
}
if (!$username) {
$this->Session->setFlash('Няма такъв потребител!');
$this->redirect(array('action' => 'index'));
}
$this->set('user', $this->User->read());
}
And in the route config:
Router::connect('/:username', array('controller' => 'users', 'action'=> 'view'), array('username' => '^([a-z0-9])+$'));
But when I try: www.example.com/Username it returns a fatal error: Missing controller.
I tried also this:
Router::connect('/users/:username', array('controller' => 'users', 'action'=> 'view'), array('pass' => array('username'), 'username' => '^([a-z0-9])+$'));
Unfortunately for this sort of setup using /:username is too simple, it will pretty much override every single other route. To do this effectively ( + the proper CakePHP way) you need to setup a custom route, here is mine which pretty much achieves the same thing. Just replace "product" with "user" for most cases, read through it though, to make sure you understand what's going on..
Look at my routes config as well if you still can't work it out.
If you want to route /user/dunhamzz to a profile you would set it up like this:
Router::connect('/user/:username',
array('controller' => 'users', 'action' => 'view'),
array('pass' => array('username')
);
Then your view action simply gets the username as the first argument:
public function view($username) {
}