Background
I am working on a project that is build with Serverless framework with serverless-appsync-plugin. I implemented a single endpoint(AWS Lambda) to handle all the requests generate from Appsync through graphQL. The endpoint will route the request to the corresponding function to perform the operation.
Problem
Now that I have developed around 10+ operations, I want to automate the process of unit-testing. For simplicity, I decided to run that single endpoint as a lambda locally for all the testing (against running appsync-offline).
So, I used lambda-local with mocha. However, I am not able to get a test-case to fail, based on the response I got from the lambda.
it('db should have a user of uId: 123', async function () {
lambdaLocal.execute({
event: makeUserEvent({userId: '123'}),
callbackWaitsForEmptyEventLoop: false,
lambdaPath,
callback: function(err, data) {
if(err) {
console.log(1)
expect.fail(null, null, 'You are not supposed to be here') //should fail here
} else {
console.log(2)
// some checking here, may fail or may not fail
expect.fail(null, null, 'return fail if the userId is 234') //should fail here too
}
}
});
console.log(3)
})
In both of the situation I want it to fail, it is not failing the test cases for either callback('failed', null) or callback(null, 'success').
So, what is the right way to make the lambda-local to fail a test case?
In your code the test finishes without mocha registering that the assertions in lambdaLocal.execute failed. So it will always pass.
Instead of using the callback parameter you could return a promise, or await the lambdalocal.execute and then do your assertions.
For example (using Promises):
it('db should have a user of uId: 123', function () {
return lambdaLocal.execute({
event: makeUserEvent({userId: '123'}),
callbackWaitsForEmptyEventLoop: false,
lambdaPath })
.then(
(data) => {
console.log(2)
// some checking here, may fail or may not fail
expect.fail(null, null, 'return fail if the userId is 234') //should fail here
},
(err) => {
console.log(1)
expect.fail(null, null, 'You are not supposed to be here') //should fail here
})
}
Alternatively change signature of the function that is passed to it to take an additional parameter (usually called done) which will then be used by mocha to pass a function that can be used to signalise that the test finished. See the mocha documentation for more info.
Related
I have added a command getCSRFToken that is used by other commands to get the CSRF token for making requests to my app:
Cypress.Commands.add("getCSRFToken", () => {
cy.getCookie('XSRF-TOKEN').then((cookie) => {
if (!cookie) {
return cy.request('HEAD', '/')
.its('headers')
.then((headers) => {
const token = headers['x-xsrf-token'];
if (!token) {
throw new Error('XSRF token not found');
}
return cy.setCookie('XSRF-TOKEN', token)
.then(() => token);
});
}
return cookie.value;
});
});
The portion that makes a HEAD request is for usage of this function when no pages have yet been visited in the test, for example when making POST requests to create test data.
AFAICT this looks like it should work to me, however it seems subsequent calls to getCookie doesn't actually retrieve anything:
I thought returning the setCookie promise and getCookie promise might make a difference but it does not seem like that is the case.
By default, Cypress clears up all cookies before every test is run. They have an api to keep a cookie for the next test execution which is Cypress.Cookies.preserveOnce
Back to your use case, you can call Cypress.Cookies.preserveOnce('XSRF-TOKEN') in the suite-level beforeEach in every suite where you want to get the token. If you don't want to repeat the call, you can move it inside your getCSRFToken command.
Cypress.Commands.add("getCSRFToken", () => {
Cypress.Cookies.preserveOnce('XSRF-TOKEN')
cy.getCookie('XSRF-TOKEN').then((cookie) => {
.....
});
});
I have a Vue component/view that performs an API request using Axios and updates the component data with the response. I'm using Moxios to mock the Axios request in unit tests.
I tried using Vue.nextTick to postpone assertion of the updated data, but the component has not updated at that point yet. If I add a delay, the assertion works correctly:
setTimeout(() => {
expect(wrapper.text()).toMatch('Updated text')
done()
}, 500)
However this is bad practice, slows down the tests and is a race condition.
Is there some kind of assertion check that would be called every time a component updates? Essentially, I'm looking for something like:
Vue.eventually(() => {
expect(wrapper.text()).toMatch('Updated text')
done()
})
A generic (non-Vue-specific) workaround:
const test = () => {
try {
expect(wrapper.text()).toMatch('Updated text')
done()
} catch (e) {
setTimeout(test, 1)
}
}
setTimeout(test, 1)
However, any failures from the expect are ignored and the test times out without any message if failing.
I am currently writing unit tests for one of my components. In particular, I have login(): void function. Here's the simplified logic:
login(): void {
this.showSpinner = true;
this.userService.login(loginData)
.subscribe(result => {
this.showSpinner = false;
}
)
}
I am struggling to write a test that checks that showSpinner property gets set to true before calling the userService.login.
Here's my test:
it('should display the spinner when the form is being saved',
inject([TestComponentBuilder], fakeAsync((tcb: any) => {
createComponent(tcb).then((fixture:ComponentFixture<any>) => {
fixture.componentInstance.login();
expect(fixture.componentInstance.showSpinner).toBe(true);
tick();
});
})));
});
And this test fails, because .subscribe gets resolved / run immediately (i tried commenting out this.showSpinner = false in my component, and the test passed).
In my userService mock, I have the following, for the login method mock:
this.loginSpy = this.spy('login').andReturn(Observable.of(this));
Where this is mockUserService.
I am confident that I am mocking userService and specifically the login method on the userService correctly, as I have other tests for this component that behave correctly.
I have also tried returning Observable.of(this).delay(1) from my spy and then calling tick(1) in my test. However that results in inconsistent behaviour in that sometimes my tests pass, but other times i get an error saying:
Error: 1 periodic timer(s) still in the queue.
How can I test the logic that precedes .subscribe()?
After more consideration I have realized that my current code does not abide by the single responsibility principle. This thought came from the fact that everyone is always repeating that you should "Refactor hard to test code".
With that in mind, I have moved all the logic that needed to be done before the call to userService.login is being made - into its own separate function. Which essentially results in:
login():void {
this.userService.login(this.loginData)
.subscribe(result => {
this.showSpinner = false;
});
}
formSubmit(): void {
this.showSpinner = true;
this.login();
}
This logic is now much easier to test.
HOWEVER we need to remember to add a spy on our login() method when we are testing formSubmit(), as if we don't, formSubmit() will simply make a call to login(), which will again complete synchronously and we will have the same problem. So my new and final test for this feature is:
it('should display the spinner when the form is being saved',
inject([TestComponentBuilder], fakeAsync((tcb: any) => {
createComponent(tcb).then((fixture:ComponentFixture<any>) => {
var loginSpy = spyOn(fixture.componentInstance, 'login');
fixture.componentInstance.formSubmit();
expect(fixture.componentInstance.showSpinner).toBe(true);
});
})));
});
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.
My UserManager service automatically fires a $http POST every hour to refresh the user access token.
I'm trying to mock that call to verify that the token is being refreshed, but when I try to flush the $httpbackend I get an error saying 'No pending requests to flush' even though I know that the refresh function has been called (added a console.log just to verify).
Either the fact that the function is being called through a setTimeOut is effecting the $httpbackend or I'm missing something else.
Code attached bellow:
describe("UserManager Service Testing", function() {
beforeEach(angular.mock.module('WebApp'));
beforeEach(inject(function($httpBackend) {
window.apiUrls = jQuery.parseJSON('{"cover_art": "http://myrockpack.com/ws/cover_art/?locale=en-us", "channel_search_terms": "http://myrockpack.com/ws/complete/channels/?locale=en-us", "register": "http://myrockpack.com/ws/register/", "categories": "http://myrockpack.com/ws/categories/?locale=en-us", "reset_password": "http://myrockpack.com/ws/reset-password/", "share_url": "http://myrockpack.com/ws/share/link/", "video_search": "http://myrockpack.com/ws/search/videos/?locale=en-us", "channel_search": "http://myrockpack.com/ws/search/channels/?locale=en-us", "video_search_terms": "http://myrockpack.com/ws/complete/videos/?locale=en-us", "popular_channels": "http://myrockpack.com/ws/channels/?locale=en-us", "popular_videos": "http://myrockpack.com/ws/videos/?locale=en-us", "login": "http://myrockpack.com/ws/login/", "login_register_external": "http://myrockpack.com/ws/login/external/", "refresh_token": "http://myrockpack.com/ws/token/"}');
var mockLogin = {"token_type":"Bearer","user_id":"oCRwcy5MRIiWmsJjvbFbHA","access_token":"752a4f939662846a787a1474ad17ffddcd816dc7AAFB1G7HvgH-0qAkcHMuTESIlprCY72xWxyiuhySCpFJxKVYqOx9W7Gt","resource_url":"http://myrockpack.com/ws/oCRwcy5MRIiWmsJjvbFbHA/","expires_in":2,"refresh_token":"fa2f47f3590240e4bdfdbde03bf8042d"}
var refreshToken = {"token_type":"Bearer","user_id":"CeGfSz6dQW2ga2P2tKb3Bg","access_token":"ef235de46dba53ba69ed049f57496ec902da5d28AAFB1HdeTE-2vwnhn0s-nUFtoGtj9rSm9waiuhySCpFJxKVYqOx9W7Gt","resource_url":"http://myrockpack.com/ws/CeGfSz6dQW2ga2P2tKb3Bg/","expires_in":3600,"refresh_token":"873e06747d964a0d80f79181c98aceac"};
$httpBackend.when('POST', window.apiUrls.refresh_token).respond(refreshToken);
$httpBackend.when('POST', window.apiUrls.login).respond(mockLogin);
}));
it('UserManager should refresh the token after 2 seconds', inject(function(UserManager, $httpBackend) {
UserManager.oauth.Login('gtest','qweqwe');
$httpBackend.flush();
waits(4000);
$httpBackend.flush();
expect(UserManager.oauth.credentials.access_token).toEqual('ef235de46dba53ba69ed049f57496ec902da5d28AAFB1HdeTE-2vwnhn0s-nUFtoGtj9rSm9waiuhySCpFJxKVYqOx9W7Gt');
}));
});
There is a discussion about this here. "Since promises are async you need to do $rootScope.$digest() in your tests to get them going"
add this before your $httpBackend.flush():
if(!$rootScope.$$phase) {
$rootScope.$apply();
}
so your code becomes:
it('UserManager should refresh the token after 2 seconds', inject(function(UserManager, $httpBackend) {
UserManager.oauth.Login('gtest','qweqwe');
if(!$rootScope.$$phase) {
$rootScope.$apply();
}
$httpBackend.flush();
expect(UserManager.oauth.credentials.access_token).toEqual('ef235de46dba53ba69ed049f57496ec902da5d28AAFB1HdeTE-2vwnhn0s-nUFtoGtj9rSm9waiuhySCpFJxKVYqOx9W7Gt');
}));
I guess you have to wrap the code below the waits inside a runs(...) block. Otherwise your code is executed immediately before the waiting has been finished
waits(4000);
runs(function() {
$httpBackend.flush();
expect(UserManager.oauth.credentials.access_token).toEqual('ef235de46dba53ba69ed049f57496ec902 da5d28AAFB1HdeTE-2vwnhn0s-nUFtoGtj9rSm9waiuhySCpFJxKVYqOx9W7Gt');
});
It's described in the jasmine docs as well: https://github.com/pivotal/jasmine/wiki/Asynchronous-specs
Your first $httpBackend.flush() flushes all the pending requests that you defined in your beforeEach().
When you call $httpBackend.flush() a second time, there are no pending requests, so you get the message.
You need to add another event(s) after the first flush.
Untested, but that's my theory.
So with my jasmine tests all the time sometimes I have had to use regex to make the url a little less specific, most of this has been with angular wanting to strip trailing slashes or other stupid little things. Such as the request url wouldn't find the expect request but the regex below would.
https://qa.com/sessions/5cdec5bde6a242dca2cf5dd0ff7be2c9
/(qa.com\/sessions\/5cdec5bde6a242dca2cf5dd0ff7be2c9)/g