How to successfully instantiate a db model in a Sails test - unit-testing

I have been following the Sails.js documentation for testing, here:
http://sailsjs.org/documentation/concepts/testing
I have successfully been able to implement Controller tests that
hit different paths of my app, and check the responses of different Express requests.
My trouble is knowing A) How to instantiate a Model, specifically from my User model B) How I can guarantee that the model is successfully created.
I currently have a test in which, in a before hook, I create new user with all the required attributes:
before(function(){
User.create({firstName:"Bob", lastName: "Balaban", password:"12345", email:"bob#bob.com"})
});
The problem is that, I do not know how to verify if this record has been added to my tests database, or if a validation error or some other error is thrown upon the call to create.
NOTE: I ask this, because a test which is dependent upon the before() hook successfully functioning fails, and the only reason it could possible fail is if the User wasn't actually added to the db

You need to wait for the User to be created in before by using the done callback function argument, and calling it after you're done with setting up the test environment. Also, you're not lifting sails here for some reason despite the docs urging you to do so. I'd also recommend using a test database instead of your normal database so your test data is independent of your production / development data.
Example code below. The addition of done and the exec callback are probably the most vital parts.
var Sails = require('sails'), sails;
// ...
before(function(done) {
// Increase the Mocha timeout so that Sails has enough time to lift.
this.timeout(10000);
Sails.lift({
// If you want to use a different DB for testing, uncomment these and replace with your own DB info.
/*connections: {
// Replace the following with whatever suits you.
testMysql: {
adapter : 'sails-mysql',
host : 'localhost',
port : 3306,
user : 'mySQLUser',
password : 'MyAwesomePassword',
database : 'testDB'
}
},
models: {
connection: 'testMysql',
migrate: 'drop'
}
*/
}, function(err, server) {
sails = server;
if (err) return done(err);
User.create({firstName:"Bob", lastName: "Balaban", password:"12345", email:"bob#bob.com"})
.exec(function(err, createdUser) {
if (err) {
console.log("Failed to create user! Error below:");
console.log(err);
}
else {
console.log("User created successfully:");
console.log(user);
}
done(err, sails);
})
});
});

Related

Ember Super Rentals Tutorial 3.15 - Working with data

I was following the ember Super Rental 3.15 tutorial, when I got to the working with data section, I updated the route index file with model hooks, the page stopped working. Also I am finding ember tutorials to be incomplete.
error says property of map is undefined
code in routes index.js file:
import Route from '#ember/routing/route';
const COMMUNITY_CATEGORIES = [
'Condo',
'Townhouse',
'Apartment'
];
export default class IndexRoute extends Route {
async model() {
let response = await fetch('/api/rentals.json');
let { data } = await response.json();
return data.map(model => {
let { attributes } = model;
let type;
if (COMMUNITY_CATEGORIES.includes(attributes.category)) {
type = 'Community';
} else {
type = 'Standalone';
}
return { type, ...attributes };
});
}
}
image if error message:
Your problem is that fetch('/api/rentals.json'); does not return the correct data. And so when you do let { data } = await response.json(); then data will be undefined and you can not do undefined.map.
So the code you posted is correct. The problem is somewhere else. You can check:
did you correctly add the rentals.json file? If you open http://localhost:4200/api/rentals.json do you see the data? So have you done this?
I see some error from mirage. The super-rentals tutorial does not use mirage. I can see this here (sidenote: that git repo is automatically created from the guides, so its always up to date). So this could be your problem. Depending how you configure mirage it will basically mock all your ajax requests. This means that fetch(... will no longer work then expected, mirage assumes you always want to use mocked data and you did not configure mirage correctly. You can try to remove mirage from your package.json, rerun npm install, restart the ember server and try it again.

Ember computed property depending on service property not updating

In my Ember 2.8 application, I'm establishing a Websocket connection in a service. The connection URL changes when a user is logged in (it then includes the user auth token as a query parameter).
The current user service is simple:
CurrentUserService = Ember.Service.extend(
name: "current-user"
user: null
load: ->
// Do some stuff
#set("user", user)
)
It works exactly as expected, and I use it to display the current users username on the page (among other things).
In the Websocket service, all I do is create a computed property, depending on currentUser.user, that sets up the connection (depending on whether a user is logged in):
ActionCableService = Ember.Service.extend(
name: "action-cable"
cable: service()
currentUser: service()
testObs: Ember.observer("currentUser", ->
console.log "currentUser changed, #{ #get("currentUser.user") }"
)
consumer: Ember.computed("currentUser.user", ->
consumerUrl = "ws://localhost:10000/cable"
if #get("currentUser").user?
consumerUrl += "?token=#{ #get("currentUser.user.authToken") }"
console.log(consumerUrl)
return #get("cable").createConsumer(consumerUrl)
)
)
Problem is, the consumer property never gets updated. It's set once, on page load, and when the user property of the currentUser service changes, consumer is not updated, and neither does my test observer.
When I refresh the page, sometimes the logged in consumerUrl is used, and sometimes it's not.
I'm guessing sometimes the session restoration happens first, and sometimes the action cable service happens first.
What I expected to happen when the action cable service gets loaded first is:
Action cable service gets loaded, no current user set yet, connect to public websocket
Logic that handles restoring user from session data fires, sets currentUser.user (this happens, I can see the username on my page)
The consumer computed property notices the currentUser.user change and connects to the private consumerUrl (does not happen)
I can very easily solve this problem in a way that does not depend on computed properties, but I would like to know what went wrong here.
Computed properties, by default, observe any changes made to the properties they depend on, and are dynamically updated when they're called.
Unless you are invoking or calling that computed property, it will not execute your intended code.
Observers, on the other hand, react without invocation, when the property they are watching, changes. But they are often overused, and can easily introduce bugs due to their synchronous nature.
You could refactor your observers and computed properties into helper functions that are called directly. This makes them easier to unit test as well.
In your controller, you can handle the initial action of logging in, like this:
currentUser: Ember.inject.service(),
actions: {
login() {
this.auth({ username: 'Mary' });
},
},
auth(data) {
// Send data to server for authentication...
// ...upon response, handle the following within the promise's `then`
// method, failures caught within `catch`, etc. But for purposes of
// demonstration, just mocking this for now...
const response = { username: 'Mary', authToken: 'xyz', };
this.get('currentUser').setConsumer(response);
},
The current-user service could then set it’s properties, and call a helper function on the action-cable service:
actionCable: Ember.inject.service(),
authToken: null,
username: null,
setConsumer(response) {
this.set('authToken', response.authToken);
this.set('username', response.username);
this.get('actionCable').setConsumer();
},
The action-cable service reads properties from currentService, sets the consumerUrl, and calls the cable service to create the consumer:
cable: Ember.inject.service(),
currentUser: Ember.inject.service(),
setConsumer() {
var consumerUrl = "ws://localhost:10000/cable";
if (this.get("currentUser.username") !== null) {
consumerUrl += "?token=" + (this.get("currentUser.authToken"));
}
console.log("ACTION CABLE SERVICE, Consumer URL: ", consumerUrl);
this.get("cable").createConsumer(consumerUrl);
}
I’ve created an Ember Twiddle to demonstrate.
Another way would be to emit an event in the service and then subscribe to the event in the init method and set the value of the dependent key of the computed property to force it to be recomputed/updated.

Ember-CLI-Mirage enforcing JSON:API?

Stumped on a couple failures and want to know if I'm understanding Mirage correctly:
1.In ember-cli-mirage, am I correct that the server response I define should reflect what my actual server is returning? For example:
this.get('/athletes', function(db, request) {
let athletes = db.athletes || [];
return {
athletes: athletes,
meta: { count: athletes.length }
}
});
I am using custom serializers and the above matches the format of my server response for a get request on this route, however, on two tests I'm getting two failures with this error: normalizeResponse must return a valid JSON API document: meta must be an object
2.Is mirage enforcing the json:api format, and is it doing so because of the way I'm setting up the tests?
For example, I have several tests that visit the above /athletes route, yet my failures occur when I use an async call like below. I would love to know the appropriate way to correctly overwrite the server response behavior, as well as why the normalizeResponse error appears in the console for 2 tests but only causes the one below to fail.
test('contact params not sent with request after clicking .showglobal', function(assert) {
assert.expect(2);
let done = assert.async();
server.createList('athlete', 10);
//perform a search, which shows all 10 athletes
visit('/athletes');
fillIn('.search-inner input', "c");
andThen(() => {
server.get('/athletes', (db, request) => {
assert.notOk(params.hasOwnProperty("contacts"));
done();
});
//get global athletes, which I thought would now be intercepted by the server.get call defined within the andThen block
click('button.showglobal');
});
});
Result:
✘ Error: Assertion Failed: normalizeResponse must return a valid JSON API document:
* meta must be an object
expected true
I tried changing my server response to a json:api format as suggested in the last example here but this looks nothing like my actual server response and causes my tests to fail since my app doesn't parse a payload with this structure. Any tips or advice must appreciated.
You are correct. Are the failures happening for the mock you've shown above? It looks to me like that would always return meta as an object, so verify the response is what you think it should be by looking in the console after the request is made.
If you'd like to see responses during a test, enter server.logging = true in your test:
test('I can view the photos', function() {
server.logging = true;
server.createList('photo', 10);
visit('/');
andThen(function() {
equal( find('img').length, 10 );
});
});
No, Mirage is agnostic about your particular backend, though it does come with some defaults. Again I would try enabling server.logging here to debug your tests.
Also, when writing asserts against the mock server, define the route handlers at the beginning of the test, as shown in the example from the docs.
I was able to get my second test to pass based on Sam's advice. My confusion was how to assert against the request params for a route that I have to visit and perform actions on. I was having to visit /athletes, click on different buttons, and each of these actions was sending separate requests (and params) to the /athletes route. That's is why I was trying to redefine the route handler within the andThen block (i.e. after I had already visited the route using the route definition in my mirage/config file).
Not in love with my solution, but the way I handled it was to move my assertion out of route handler and instead assign the value of the request to a top-level variable. That way, in my final andThen() block, I was able to assert against the last call to the /athletes route.
assert.expect(1);
//will get assigned the value of 'request' on each server call
let athletesRequest;
//override server response defined in mirage/config in order to
//capture and assert against request/response after user actions
server.get('athletes', (db, request) => {
let athletes = db.athletes || [];
athletesRequest = request;
return {
athletes: athletes,
meta: { count: athletes.length }
};
});
//sends request to /athletes
visit('/athletes');
andThen(() => {
//sends request to /athletes
fillIn('.search-inner input', "ab");
andThen(function() {
//sends (final) request to /athletes
click('button.search');
andThen(function() {
//asserts against /athletes request made on click('button.search') assert.notOk(athletesRequest.queryParams.hasOwnProperty("contact"));
});
});
});
I'm still getting console errors related to meta is not an object, but they are not preventing tests from passing. Using the server.logging = true allowed me to see that meta is indeed an object in all FakeServer responses.
Thanks again to Sam for the advice. server.logging = true and pauseTest() make acceptance tests a lot easier to troubleshoot.

How do i unit test the auth filter in Laravel 4.1?

I want to write a unit test that should check if an unauthenticated user can view the user list (which he shouldnt be able to).
My routes
Route::group(array('prefix' => 'admin'), function() {
Route::get('login', function() {
return View::make('auth.login');
});
Route::post('login', function() {
Auth::attempt( array('email' => Input::get('email'), 'password' => Input::get('password')) );
return Redirect::intended('admin');
});
Route::get('logout', 'AuthController#logout');
Route::group(array('before' => 'auth'), function() {
Route::get('/', function() {
return Redirect::to('admin/users');
});
Route::resource('users', 'UsersController');
});
});
My test
public function testUnauthenticatedUserIndexAccess() {
$response = $this->call('GET', 'admin/users');
$this->assertRedirectedTo('admin/login');
}
My filter
Route::filter('auth', function() {
if (Auth::guest()) return Redirect::guest('admin/login');
});
Result
Failed asserting that Illuminate\Http\Response Object (...) is an instance of class "Illuminate\Http\RedirectResponse".
If i log the $response from the test, it shows the full user list like if an admin was logged in during testing.
If i browse to admin/users using a browser without logging in I'm redirected to login like i should, so the auth filter is indeed working.
Questions
Is there something in Laravel that logs in the first user during testing for you by default? Or is Auth::guest() always false by default during testing?
If so, how do i become "logged out" during unit testing? I tried $this->be(null) but got an error saying the object passed must implement UserInterface.
Laravel filters are automatically disabled for unit tests; you need to enable them for a specific test case.
Route::enableFilters();
Or this if you're not keen on using the static facades
$this->app['router']->enableFilters();
Take a look in the unit testing documentation.
Your test should now return the correct object type allowing your test to pass.

How should errors be handled when using the Ember.js Data RESTAdapter?

ember-data.js: https://github.com/emberjs/data/tree/0396411e39df96c8506de3182c81414c1d0eb981
In short, when there is an error, I want to display error messages in the view, and then the user can 1) cancel, which will rollback the transaction 2) correct the input errors and successfully commit the transaction, passing the validations on the server.
Below is a code snippet from the source. It doesn't include an error callback.
updateRecord: function(store, type, record) {
var id = get(record, 'id');
var root = this.rootForType(type);
var data = {};
data[root] = this.toJSON(record);
this.ajax(this.buildURL(root, id), "PUT", {
data: data,
context: this,
success: function(json) {
this.didUpdateRecord(store, type, record, json);
}
});
},
Overall, what is the flow of receiving an error from the server and updating the view? It seems that an error callback should put the model in an isError state, and then the view can display the appropriate messages. Also, the transaction should stay dirty. That way, the transaction can use rollback.
It seems that using store.recordWasInvalid is going in the right direction, though.
This weekend I was trying to figure the same thing out. Going off what Luke said, I took a closer look at the ember-data source for the latest commit (Dec 11).
TLDR; to handle ember-data update/create errors, simply define becameError() and becameInvalid(errors) on your DS.Model instance. The cascade triggered by the RESTadapter's AJAX error callback will eventually call these functions you define.
Example:
App.Post = DS.Model.extend
title: DS.attr "string"
body: DS.attr "string"
becameError: ->
# handle error case here
alert 'there was an error!'
becameInvalid: (errors) ->
# record was invalid
alert "Record was invalid because: #{errors}"
Here's the full walk through the source:
In the REST adapter, the AJAX callback error function is given here:
this.ajax(this.buildURL(root, id), "PUT", {
data: data,
context: this,
success: function(json) {
Ember.run(this, function(){
this.didUpdateRecord(store, type, record, json);
});
},
error: function(xhr) {
this.didError(store, type, record, xhr);
}
});
didError is defined here and it in turn calls the store's recordWasInvalid or recordWasError depending on the response:
didError: function(store, type, record, xhr) {
if (xhr.status === 422) {
var data = JSON.parse(xhr.responseText);
store.recordWasInvalid(record, data['errors']);
} else {
store.recordWasError(record);
}
},
In turn, store.recordWasInvalid and store.recordWasError (defined here) call the record (a DS.Model)'s handlers. In the invalid case, it passes along error messages from the adapter as an argument.
recordWasInvalid: function(record, errors) {
record.adapterDidInvalidate(errors);
},
recordWasError: function(record) {
record.adapterDidError();
},
DS.Model.adapterDidInvalidate and adapterDidError (defined here) simply send('becameInvalid', errors) or send('becameError') which finally leads us to the handlers here:
didLoad: Ember.K,
didUpdate: Ember.K,
didCreate: Ember.K,
didDelete: Ember.K,
becameInvalid: Ember.K,
becameError: Ember.K,
(Ember.K is just a dummy function for returning this. See here)
So, the conclusion is, you simply need to define functions for becameInvalid and becameError on your model to handle these cases.
Hope this helps someone else; the docs certainly don't reflect this right now.
DS.RESTAdapter just got a bit more error handling in this commit but we are still not yet at a point where we have a great recommendation for error handling.
If you are ambitious/crazy enough to put apps in production today with ember-data (as I have been!), it is best to make sure that the likelihood of failures in your API is extremely low. i.e. validate your data client-side.
Hopefully, we can update this question with a much better answer in the coming months.
I just ran into such a situation, not sure if this is already explained anywhere.
I am using:
Em.VERSION : 1.0.0
DS.VERSION : "1.0.0-beta.6"
Ember Validations (dockyard) : Version: 1.0.0.beta.1
Ember I18n
The model was initially mixedin with Validation mixin.
App.Order = DS.Model.extend(Ember.Validations.Mixin, {
.....
someAttribute : DS.attr('string'),
/* Client side input validation with ember-validations */
validations : {
someAttribute : {
presence : {
message : Ember.I18n.t('translations.someAttributeInputError')
}
}
}
});
In the template, corresponding handlebars is added. (note that ember validations will automatically add errors to model.errors.<attribute> in case of input validations, I will be using same trade-off in server validations as well)
<p>{{t 'translations.myString'}}<br>
{{view Ember.TextField valueBinding="attributeName"}}
{{#if model.errors.attributeName.length}}<small class="error">{{model.errors.attributeName}}</small>{{/if}}
</p
Now, we will be saving the Order
App.get('order').save().then(function () {
//move to next state?
}, function(xhr){
var errors = xhr.responseJSON.errors;
for(var error in errors){ //this loop is for I18n
errors[error] = Ember.I18n.t(errors[error]);
}
controller.get('model').set('errors', errors); //this will overwrite current errors if any
});
Now if there is some validation error thrown from server, the returned packet being used is
{"errors":{"attributeName1":"translations.attributeNameEror",
"another":"translations.anotherError"}}
status : 422
It is important to use status 422
So this way, your attribute(s) can be validated client side and again on server side.
Disclaimer : I am not sure if this is the best way!
Since there's currently no good solution in stock Ember-Data, I made my own solution by adding an apiErrors property to DS.Model and then in my RestAdapter subclass (I already needed my own) I added error callbacks to the Ajax calls for createRecord and updateRecord that save the errors and put the model in the "invalid" state, which is supposed to mean client-side or server-side validations failed.
Here's the code snippets:
This can go in application.js or some other top-level file:
DS.Model.reopen({
// Added for better error handling on create/update
apiErrors: null
});
This goes in the error callbacks for createRecord and updateRecord in a RestAdapter subclass:
error: function(xhr, textStatus, err) {
console.log(xhr.responseText);
errors = null;
try {
errors = JSON.parse(xhr.responseText).errors;
} catch(e){} //ignore parse error
if(errors) {
record.set('apiErrors',errors);
}
record.send('becameInvalid');
}