I'm trying to define a function that sends a request in a collection Pre-Request scripts:
const doRequest = (callback) => {
const echoPostRequest = {
url: 'https://postman-echo.com/post',
method: 'POST',
header: 'headername1:value1',
body: {
mode: 'raw',
raw: JSON.stringify({ key: 'this is json' })
}
};
console.log('ready to send request');
pm.sendRequest(echoPostRequest, function (err, res) {
console.log('request sent', err ? err : res.json());
callback();
});
}
glbl = {
doRequest: doRequest
}
Then, in my main test (a simple GET to google), I have this in Pre-Request script:
glbl.doRequest(() => console.log('works!'));
However, the callback is never called, and the "request sent" log is never printed.
This is the output of my postman console:
ready to send request 11:58:02.257
GET http://www.google.com 11:58:02.262
Do you know what I'm doing wrong?
I can provide the exported collection as well if it helps.
Thanks!
Edit: it I move everything in the Pre-request scripts of the request (not the collection), everything works fine
There's a few things happening here.
Local scoping issues
Your glbl variable is missing a var, const or let keyword.
Missing this keyword does not cause the variable to bubble up and become global on it's own. The environment Pre-request Script and request Pre-request Script are of different scopes.
As you've experienced, when you move the script to solely the request level, everything is sitting in the same scope so this works fine.
Creating global functions
Postman has the facility to create your own global variables. This can be done programmatically through pm.globals.set and pm.globals.get.
https://www.getpostman.com/docs/v6/postman/environments_and_globals/variables#accessing-variables-through-scripts
There are some limitations to these variables: you can only store strings in them, so the object and function that you've created will not persist if we don't do something to change their type.
In this case before we set any variables we need to ensure we:
toString any functions
JSON.stringify any objects
Now the Pre-request script for our collection will look like:
const doRequest = (callback) => {
const echoPostRequest = {
url: 'https://postman-echo.com/post',
method: 'POST',
header: 'headername1:value1',
body: {
mode: 'raw',
raw: JSON.stringify({ key: 'this is json' })
}
};
console.log('ready to send request');
pm.sendRequest(echoPostRequest, function (err, res) {
console.log('request sent', err ? err : res.json());
callback();
});
};
const glbl = {
doRequest: doRequest.toString()
};
pm.globals.set('glbl', JSON.stringify(glbl));
In order to use this at a request level, we need to update our Pre-request Script there too:
const glbl = JSON.parse(pm.globals.get('glbl'));
const doRequest = eval(glbl.doRequest);
doRequest(() => console.log('works!'));
After several tests I found a very ugly hack to export JS objects from a pre-request script to an other.
Of curse you have to ensure that the script that expose values is called before the module that uses the exported stuff.
The hack came from the following considerations:
You can't export js values other than string in postman, but you can add properties in the modules exposed by nodejs, so I imported the "util" module of nodejs and injected stuff there.
const util = require("util");
util.myExport = {
myMethod(){
// ...
}
};
The pm object exposed is different in each script executed by postman (require("util").myExport.pm === pm returns false if require("util").myExport.pm was defined in another module), and only the current one can perform operation, so if I want to use pm in some of the exported method, I have to pass a reference of the current pm object. otherwise no method can be invoked.
const util = require("util");
util.myExport = {
myMethod(_pm){ // note that `_pm` is the reference passed here,
_pm.sendRequest(/*...*/) // otherwise this line wont work
}
};
If you define an asynchronous function (such as a wrapper for setTimeout), it wont open the new process (nor thread, as well as with Promise or async/await functions) unless you use the properties exposed in the caller module. So you can not perform asynchronous operation if you are not using the current module stuff.
const util = require("util");
util.myExport = {
myMethod(scope){
scope.setTimeout(() => { // async function from the caller module
scope.pm.sendRequest(/*...*/) // pm from the caller module
}, 9999)
}
};
Now you are ready to go! Just import the nodejs "util" module to have your functions in the current module, then you can call them as usual.
const util = require("util");
util.myExport.myMethod(this)
Related
I am trying to call a graphql and get the data from cookies, it runs well in postman app. However when I trying to run this postman collection on the command line with Newman
In terminal:
newman run postman_collection.json -e environment.json
then it gave me the error
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block,
or by rejecting a promise which was not handled with .catch().
The promise rejected with the reason "TypeError: CookieJar.getAll() requires a callback function".]
{
code: 'ERR_UNHANDLED_REJECTION'
}
And the Test script code is like this
pm.test("Get a test data", function () {
const jsonData = pm.response.json();
pm.expect(jsonData.data.createTest.success).to.eql(true);
});
pm.test("Test data cookies set", async function () {
const cookieJar = pm.cookies.jar();
const url = pm.environment.get("service-url");
const cookies = await cookieJar.getAll(url);
const cookieNames = cookies.map(cookie => cookie.name);
pm.expect(cookieNames).to.include("test-token");
pm.expect(cookieNames).to.include("legacy-test-token");
});
So I assume the error is because getAll() requires a callback function, Do you know what I'm doing wrong? How can I improve it, Can you help me solve this? Many thanks
'it runs well in postman app' --> I doubt it. I tried and it always passed.
I added a callback, also changed a setting Whitelist Domain in Postman GUI.
pm.test("Test data cookies set", function () {
const cookieJar = pm.cookies.jar();
const url = pm.environment.get("service-url");
cookieJar.getAll(url, (error, cookies)=> {
if(error) console.log(error);
const cookieNames = cookies.map(cookie => cookie.name);
pm.expect(cookieNames).to.include("test-token");
pm.expect(cookieNames).to.include("legacy-test-token");
});
});
Im using newman to run api tests after build in travis.
Im trying to limit the duplication of pre-request scripts so checked out some workarounds on how can I have pre-request-scripts at collection level.
My problem is that I dont want to run them on every request, only the ones where I need them.
Example: Im trying to run a login script to use the returned token on private endpoints.
My code looks like:
Collection level pre-request script definiton:
Object.prototype.login = function() {
const request = {
url: 'somthing',
method: 'GET',
header: 'Content-Type:application/json',
body: {
mode: 'application/json',
raw: JSON.stringify(
{
email: pm.environment.get('someenv'),
password: pm.environment.get('someenv')
})
}
};
pm.sendRequest(request, function (err, res) {
var response = res.json();
pm.environment.set("token", response.token);
});
}
Request level pre-request script definiton:
_.login();
Can someone help me out why I cant run pm.sendRequest in this scope?
pm.environment.get('someenv') works like a charm, so Im not sure what to do here.
It runs fine when called from Collection level pre-request script without using the Object, but if I just put the whole request there, it will run before every request what I want to avoid in the first place.
I have tried to log some stuff out using console.log(), but it seems that the callback in pm.sendRequest() never runs.
So I have found a workaround for the issue, I hope its going to help out someone in the future :)
So its easy to setup a collection level pre-request that runs before every single request.
But to optimize this a little bit because you dont need to run every script for every request you make in a collection. You can use my solution here. :)
The issue I think is caused by:
PM object used in a different scope is not going to affect the PM object in global scope, so first you should pass global PM object as parameter for function call.
The collection level request should look like this:
login = function (pm) {
const request = {
url: pm.environment.get('base_url') + '/login',
method: 'POST',
header: {
'Content-Type': 'application/json',
},
body: {
mode: 'application/json',
raw: JSON.stringify({
email: pm.environment.get('email'),
password:pm.environment.get('passwd')
})
}
};
pm.sendRequest(request, (err, res) => {
var response = res.json();
pm.expect(err).to.be.a('null');
pm.expect(response).to.have.property('token')
.and.to.not.be.empty;
pm.globals.set("token", response.token);
});
};
And for the exact request where you want to call auth first and use the token for the request call:
login(pm);
My Controller function definition looks like that:
async login(#Req() request, #Body() loginDto: LoginDto): Promise<any> {
How I could prepare/mockup Request to provide first argument of function from Jest test?
Inside funciton I am setting headers using request.res.set. Should I somehow pass real Request object to function and then check if header is set or rather mockup whole Request object and check if set function was called?
I managed to do that mocking requests and response using node-mocks-http library.
const req = mocks.createRequest()
req.res = mocks.createResponse()
and then passing this as an argument.
const data = await authController.login(req, loginDto)
expect(req.res.get('AccessToken')).toBe(token.accessToken)
I followed a different approach and instead of using node-mocks-http library I used #golevelup/ts-jest, also, instead of testing if the function returns some value, like res.json() or res.status(), I checked if the function was called with the value I wanted to.
I borrowed this approach from Kent C. Dodds's testing workshop, take a look for similar ideas. Anyway, this is what I did in order to mock the Response dependency of my Controller's route:
// cars.controller.spec.ts
import { createMock } from '#golevelup/ts-jest';
const mockResponseObject = () => {
return createMock<Response>({
json: jest.fn().mockReturnThis(),
status: jest.fn().mockReturnThis(),
});
};
... ommited for brevity
it('should return an array of Cars', async () => {
const response = mockResponseObject();
jest
.spyOn(carsService, 'findAll')
.mockImplementation(jest.fn().mockResolvedValueOnce(mockedCarsList));
await carsController.getCars(response);
expect(response.json).toHaveBeenCalledTimes(1);
expect(response.json).toHaveBeenCalledWith({ cars: mockedCarsList });
expect(response.status).toHaveBeenCalledTimes(1);
expect(response.status).toHaveBeenCalledWith(200);
});
And that's it, I think that the implementation details aren't that important but in any case I'll leave the link to the Github repo where you can find the whole project.
I'm trying to test my 'Container' component which handles a forms logic. It is using vue-router and the vuex store to dispatch actions to get a forms details.
I have the following unit code which isn't working as intended:
it('On route enter, it should dispatch an action to fetch form details', () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
wrapper.vm.$options.beforeRouteEnter[0]();
expect(getFormDetails.called).to.be.true;
});
With the following component (stripped of everything because I don't think its relevant (hopefully):
export default {
async beforeRouteEnter(to, from, next) {
await store.dispatch('getFormDetails');
next();
}
};
I get the following assertion error:
AssertionError: expected false to be true
I'm guessing it is because I am not mounting the router in my test along with a localVue. I tried following the steps but I couldn't seem to get it to invoke the beforeRouteEnter.
Ideally, I would love to inject the router with a starting path and have different tests on route changes. For my use case, I would like to inject different props/dispatch different actions based on the component based on the path of the router.
I'm very new to Vue, so apologies if I'm missing something super obvious and thank you in advance for any help! 🙇🏽
See this doc: https://lmiller1990.github.io/vue-testing-handbook/vue-router.html#component-guards
Based on the doc, your test should look like this:
it('On route enter, it should dispatch an action to fetch form details', async () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
const next = sinon.stub()
MyComponent.beforeRouteEnter.call(wrapper.vm, undefined, undefined, next)
await wrapper.vm.$nextTick()
expect(getFormDetails.called).to.be.true;
expect(next.called).to.be.true
});
A common pattern with beforeRouteEnter is to call methods directly at the instantiated vm instance. The documentation states:
The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component instance via `vm`
})
}
This is why simply creating a stub or mock callback of next does not work in this case. I solved the problem by using the following parameter for next:
// mount the component
const wrapper = mount(Component, {});
// call the navigation guard manually
Component.beforeRouteEnter.call(wrapper.vm, undefined, undefined, (c) => c(wrapper.vm));
// await
await wrapper.vm.$nextTick();
How is it possible to access the loopback context (or simple Express req object) from within the model's logic?
It is critical to be able to know more about the request itself (current user identity more than anything else) inside the model's logic. When I override a built-in method (via custom script or from the model.js file) or develop a custom remote method, I would like to access the Express req object.
As loopback.getCurrentContext() is declared to be buggy, I cannot use it.
Ideas?
PS:
I find this page confusing: http://loopback.io/doc/en/lb2/Using-current-context.html
First it's said (and marked in red as important!) it is not recommended to use LoopBackContext.getCurrentContext() and then it's used it in each example!?
What's the point to give examples that do not work? Should we simply ignore the complete page? If so, what about the context? :)
Any clarification on this topic is much appreciated.
You can get access to express req object by using remote hooks
var loopback = require('loopback');
module.exports = function (MyModel) {
MyModel.beforeRemote('findOne', function (ctx, model, next) {
//access to ctx.req
console.log(ctx.req.headers)
next()
})
MyModel.beforeRemote('my-custom-remote-method', function (ctx, model, next) {
console.log(ctx.req.headers)
next()
})
}
Sure, you can use a beforeRemote hook to modify the ctx.args property. This property is the input of the remote method (that is, custom or built-in). This way, you can copy a part of the request inside this property, and it will be passed to the build-in method
Example 1 with the built-in method findOne.
MyModel.beforeRemote('findOne', function (ctx, model, next) {
ctx.args.filter.extrafield = ctx.req.headers['some-header'];
next();
});
Then override the findOne method since it's what you want to do
MyModel.on('dataSourceAttached', function(obj){
var findOne = MyModel.findOne;
MyModel.findOne = function(filter, cb) {
console.log(filter.extrafield); // Print what was in the header
return findOne.apply(this, arguments);
};
});
And finally call the method with curl
curl -H "some-header: 'hello, world!'" localhost:3000/api/MyModel/findOne
Example 2 with a custom remote printToken, to help you understand further
MyModel.beforeRemote('printToken', function (ctx, model, next) {
ctx.args.token = ctx.req.headers['some-header'];
next();
});
MyModel.printToken = function(token, cb) {
console.log(token);
cb();
}
MyModel.remoteMethod(
'printToken',
{
accepts: {arg: 'token', type: 'string', optional: true}
}
);
Then call the remote with curl, and pass the expected header
curl -H "some-header: 'hello, world!'" localhost:3000/api/MyModel/printToken
EDIT: There is a simpler solution that only works for custom remote
When defining your remote method, it is possible to tell loopback to pass elements of the http request to your remote directly as an input argument
MyModel.remoteMethod(
'printToken',
{
accepts: [
{arg: 'req', type: 'object', 'http': {source: 'req'}},
{arg: 'res', type: 'object', 'http': {source: 'res'}}
]
}
);
This way, your remote can access the req and res objects. This is documented here