I am trying to unit test a component in a Vue.js and Apollo app. The component's general behavior is to query the server, load the response into the data of the Vue component. When a button is pushed run a GraphQL mutation and then update the data with the response data. Here's a stub of the component.
Vue.extend({
$apollo: {
person: {
query: gql`query($id: ID!) {
person(id: $id) {
name
id
}
}`,
fetch-policy: 'network-only',
}
},
data() {
return {
person: {},
newPersonId: null,
}
},
methods: {
getNewPerson() {
this.$apollo.mutate({
mutation: gql`mutation($person: Person!) {
savePerson(person: $Person) {
person {
id
}
}
}`,
update: (store, { data }) {
////////////////////////////
// test-what-happens-here //
////////////////////////////
}
})
}
}
});
My gut says the appropriate tests to write are
it('renders the name in correctly on load')
it('sends the correct data with the mutation')
it('does the right thing when the server responds') <--- Most Important
it('does the right thing when the server responds with an error') <--- Most Important
I've written a handful of tests but none seem to be a good pattern for testing the components round trip.
If this were an axios request I would do mock the response to send a 200 and the correct data, and assert axios request was called with the right JSON and the component looked correct, based on a stubbed response.
I cannot for the life of me figure this out with vue-apollo.
They have a decent testing section but the "simple tests" that use apollo cannot test the outcome of a request, and the graphql-tools tests don't actually leverage apollo so it misses a pretty significant amount of behavior.
How would you test the update function here in the above code?
Related
I need to test (Jest) a Redux middleware that retrieves and dispatches a series of previously saved actions. It uses the 'await' operator so that the actions are completed in sequence.
This works in operation, but in testing only the first action in the sequence is popped from the saved array and dispatched.
I have tried a number of variations of the patterns found in the Redux docs, including using redux-mock-store, with no joy. I'm new to Jest (and React/Redux unit testing in general) and this is also my first crack at middleware. I feel like its the structure of the loop that is preventing the test from iterating but I can't wrap my head around how to go about it.
The guts of the middleware looks like this:
//there are 3 saved actions in the test
const actions = getState().actions
while (actions.length > 0) {
const action = actions.pop()
await Promise.resolve(next(action))
.then(()=>{
console.log('this hits 3 times')
})
}
My initial naive try at a test was like this :
it(`dispatches all saved actions on request`, () => {
_values['state'].rewind = {
actions: {
abcdef: [
{ type: 'TEST', payload: { value: 'rewind value 1' }},
{ type: 'TEST', payload: { value: 'rewind value 2' }},
{ type: 'TEST', payload: { value: 'rewind value 3' }}]
}
}
const action = createTriggerAction('abcdef')
const { next, store, invoke } = create() // spy/mocks as in Redux Docs
invoke(action)
expect(next).toHaveBeenCalledTimes(3); // fails, called only once
})
I muddled around with making 'Invoke' an async function and 'await'ing the mocked 'next' etc. but no help.
Any pointers as to how to best do this would be hugely appreciated!
// global
var ws = null;
var msgCount = 0;
var onMessageHandler = null;
// test #1 - this test passes successfully
describe('connect to wsserver test', function() {
it('should successfully connect to wsserver', function(done) {
this.timeout(0);
ws = new FayeWebSocket.Client('wss://server.com', null, {
headers: {
"authToken": "someToken"
}
});
ws.on('open', function() {
done();
});
ws.on('message', function(msg) {
if (onMessageHandler && typeof onMessageHandler === 'function') {
onMessageHandler(msg);
}
});
ws.on('close', function(event) {
console.log('closing websocket!');
});
ws.on('error', function(err) {
console.log("error!: " + err.message);
});
});
});
// test #2 - this test blocks indefinitely
describe('send request and get back 3 response messages', function() {
it('should get back 3 response messages from the wsserver', function(done) {
this.timeout(0);
// this function is called ONLY once, although the server is sending 3 messages
onMessageHandler = function(msg) {
msgCount++;
console.log(msg);
if (msgCount >= 3) {
done();
}
}
var sendThisRequest = {
'some': 'json',
'with': 'some key/value pairs'
}
// this line sends a request to the wsserver
ws.send(JSON.stringify(sendMsg));
});
});
I'm trying to write some basic unit tests to test my websocket apis. These mocha tests are simulating the client, NOT the websocket server.
In the first test I just connect to the websocket server using a websocket, this test passes successfully.
In the second test I'm sending a request to the server ( from ws.send(message) ), the server does get this request correctly, does some processing and sends 3 websocket messages to the client. (Looking at the server logs, I can say that this part is working fine on the server)
The test should complete after getting the 3 messages and some expect(something).to.equal(something) assertions
I haven't been able to figure out so far why the 2nd and 3rd messages are never picked up by the onMessageHandler(). I've tried placing the ws.on('message',function(msg){..}) block at different places in the code but to no avail.
If someone could point me in the right direction for this problem, that would be great. Thanks
I think you are not able to do streaming of data with current unit test with mocha.
ws.send(JSON.stringify(sendMsg));
You are calling this statement only once which means only one time data is streamed. You need to call it multiple time to check n time streaming.
This code works correctly. There was some problem with the inputs that I was passing to the server. Sorry!
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.
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);
})
});
});
As you know, inside unit tests it's built-in angularjs feature to mock XHR requests with $httpBackend - this is nice and helpful while writing unit tests.
Recently, I met with need of mocking XHR in case of file upload and discovered some problems.
Consider following code:
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress(event), false);
xhr.addEventListener("load", uploadComplete(event), false);
xhr.addEventListener("error", uploadError(event), false);
xhr.addEventListener("abort", uploadAbort(event), false);
xhr.open("POST", 'some url');
xhr.send(someData);
What I want to do is to do unit testing of such a code with mocking of XHR requests, but it's not possible do it because there is no $http service used here.
I tried this (and it's working and could be mocked with $httpBackend):
$http({
method: 'POST',
url: 'some url',
data: someData,
headers: {'Content-Type': undefined},
transformRequest: angular.identity})
.then(successCallback, errorCallback);
But in this case I don't know how to implement 'progress' callback and 'abort' callback (they are essential and required in case I am working on now).
I've seen information that latest Angular supports progress callback for promises (not sure though whether it's integrated with $http service), but what about abort callback?
Any ideas or maybe your met with something similar before?
If the $http service doesn't give you everything you need, you can still unit test the first block of code. First of all, change your code to use Angular's $window service. This is just a wrapper service, but it allows you to mock the object in your tests. So, you'll want to do this:
var xhr = new $window.XMLHttpRequest();
Then in your tests, just mock it and use spies.
$window.XMLHttpRequest= angular.noop;
addEventListenerSpy = jasmine.createSpy("addEventListener");
openSpy = jasmine.createSpy("open");
sendSpy = jasmine.createSpy("send");
xhrObj = {
upload:
{
addEventListener: addEventListenerSpy
},
addEventListener: addEventListenerSpy,
open: openSpy,
send: sendSpy
};
spyOn($window, "XMLHttpRequest").andReturn(xhrObj);
From there, you can make the different spies return whatever you want for the different tests.
You should mock $http and control any deferred, as you want more control over your test. Basically, mock $http provider and serve a custom implementation that exposes its deferred, then play with it.
You should not worry whether $http is working right or not, because it is supposed to, and is already tested. So you have to mock it and only worry testing your part of the code.
You should go something like this:
describe('Testing a Hello World controller', function() {
beforeEach(module(function($provide) {
$provide.provider('$http', function() {
this.$get = function($q) {
return function() {
var deferred = $q.defer(),
promise = deferred.promise;
promise.$$deferred = deferred;
return promise;
}
};
});
}));
it('should answer to fail callback', inject(function(yourService, $rootScope) {
var spyOk = jasmine.createSpy('okListener'),
spyAbort = jasmine.createSpy('abortListener'),
spyProgress = jasmine.createSpy('progressListener');
var promise = yourService.upload('a-file');
promise.then(spyOk, spyAbort, spyProgress);
promise.$$deferred.reject('something went wrong');
$rootScope.$apply();
expect(spyAbort).toHaveBeenCalledWith('something went wrong');
}));
});
And your service is simply:
app.service('yourService', function($http) {
return {
upload: function(file) {
// do something and
return $http({...});
}
};
});
Just note that promises notification is only available in the latest RC release. So, if you can't use it, just elaborate a little more the example and mock the XHR events and so.
Also note that you should preferably have one test case for each of the callbacks (fail, success and progress), in order to follow KISS principle.