I'm writing a test for my Hubot (which acts as a Slack bot). Triggered by certain Slack messages, the bot sends an HTTP POST request to a separate Rails app. How can I check in my test script that the HTTP request has been sent? My guess was that I should check the contents of the robot.logger (please let me know if there's a better way) - but if so, how can I access the log in the test?
Hubot script (basically, it informs the Rails app about a user who is leaving the office to take a break):
module.exports = (robot) ->
robot.respond /off to lunch/i, (res) ->
res.reply('Later alligator')
robot.logger.info "This user is going on lunch break: #{res.message.user.id}"
data = JSON.stringify({
slack_user_id: res.message.user.id
})
robot.http(process.env.RAILS_APP_URL + '/break')
.header('Content-Type', 'application/json')
.post(data) (err, resp, body) ->
if err
robot.logger.info "Encountered an error. #{err}"
res.reply('Sorry, there was an error recording your break time')
else
robot.logger.info 'Successfully sent HTTP POST request to Rails app'
Log output when I execute this script:
INFO This user is going on lunch break: [SLACK_USER_ID]
INFO Successfully sent HTTP POST request to Rails app
As I mentioned above, I'd like to check in my test script that the HTTP request was sent, by asserting that the log is going to include the message 'Successfully sent HTTP POST request to Rails app'. However, I don't know how to access the Hubot's log in my test. I thought it would have something to do with process.stdout because the bot logs to stdout, but I couldn't get it to work.
Test script:
Helper = require('hubot-test-helper')
helper = new Helper('../scripts/break-start.coffee')
request = require('request')
expect = require('chai').expect
nock = require('nock')
describe 'bot responds to user message and sends ', ->
beforeEach ->
# Set up the room before running the test.
#room = helper.createRoom()
# Set up a request interceptor.
nock(process.env.RAILS_APP_URL)
.post('/break', { slack_user_id: 'bob' })
.reply(200)
afterEach ->
# Tear down the room after the test to free up the listener.
#room.destroy()
context 'user sends lunch message', ->
beforeEach ->
#room.user.say('bob', '#hubot Off to lunch')
it 'responds to users who are off to lunch', ->
expect(#room.messages).to.eql [
['bob', '#hubot Off to lunch']
['hubot', '#bob Later alligator']
# I want to do something like this:
# expect(robot.log).include('Successfully sent HTTP POST request to Rails app')
Of course, I can see in the console log when I run the test that the HTTP request is being sent, but I'd also like to assert it so that the test fails if the request is not sent.
Log output when test is executed:
INFO This user is going on lunch break: bob
✓ responds to users who are off to lunch
INFO Successfully sent HTTP POST request to Rails app
Thank you for any help in advance.
I wouldn't advise writing tests depending on logs. The log is a side-effect of the program. If you change the log output, the tests will fail, even though the functionality is still correct.
Instead you should use a library to mock out and check if the http request was performed. Actually making the request would be a side-effect, and again shouldn't be done in your tests (what if you cause excessive load on an external service due to tests running?
You are already using the nock library to catch the request. It can also be used to check if the request was made (see the expectations docs from the nock repo).
Here is an example using the requestScope.done() from nock in your test.
it 'sends the break request to the rails server', ->
# capture the request to the rails app
railsRequest = nock(process.env.RAILS_APP_URL)
.post('/break', { slack_user_id: 'bob' })
.reply(200)
# make the request and wait for it to be completed
await #room.user.say('bob', '#hubot Off to lunch')
# check that the request was made
railsRequest.done()
I'm using await to ensure the function call which should make the request is completed before testing for it. If you're not familiar with await, you can move the check (railsRequest.done()) into a .then() promise handler on the #room.user.say(...) call instead.
Promise version:
Regarding your comment, here is the promisified version. You need to pass .then a function. If you pass it .then request.done() then the request.done() expectation will be executed immediately and its result will be passed as the promise callback. Either wrap it in another function (see below) or remove the parenthesis (.then request.done). But be careful with the second option. If your promise returns a value, this will be passed to the callback. As it's from a library, this may cause unexpected behaviour - that's why I would suggest the first option:
it 'sends the break request to the rails server', ->
# capture the request to the rails app
railsRequest = nock(process.env.RAILS_APP_URL)
.post('/break', { slack_user_id: 'bob' })
.reply(200)
# make the request and wait for it to be completed
#room.user.say('bob', '#hubot Off to lunch').then ->
# check that the request was made
railsRequest.done()
Related
I have been trying to intercept a server request using Cypress' intercept method.
I have noticed that Cypress can intercept requests made through the front-end/browser, however, the intercept method doesn't work if I make a request directly to the back-end server.
Let me clarify what I mean:
One thing is intercepting a request that the front-end/browser makes to the back-end server.
Another thing is intercepting a call that doesn't use the browser but calls directly the back-end endpoint.
For example:
I can create a user using the front-end interface
or I can create a user calling the back-end endpoint directly (directly calling the server).
Coming back to my question. Is there a way to intercept a call that was made directly to the back-end endpoint?
This is what I have tried so far:
I wrote a regex to intercept api/v0/customers
I then made a request to http://locahost:5440/api/v0/customers (which is the URL of the server)
Finally, I waited for the request to happen
Timeout request using Cypress intercept method
cy.intercept(/^\/api\/v0\/customers\/$/).as('createCustomer');
cy.request(createCustomer(customerData, headers));
cy.wait('#createCustomer').then(({ status, body }) => {
const customerId = body.customer_id;
console.log(body);
expect(status).equal(201);
});
Here's the problem: There was a timeout error.
As you can see in the image, I'm making a request to http://locahost:5440 which is the server URL. NOTE: I made sure the server was up and running.
The regex is also correct and it will match the endpoint http://locahost:5440/api/v0/customers
I suspect that intercept only works for requests being made through the browser. Is this assertion correct? I couldn't find this answer anywhere in the Cypress docs.
Is there a way for me to intercept a call being made directly to a server (not using the browser)?
You don't have to intercept the requests you explicitly make with cypress, just use .then to get the response, like this:
cy.request(createCustomer(customerData, headers)).then((response) => {
const customerId = response.body.customer_id;
console.log(response.body);
expect(response.status).equal(201);
});
Reference: https://docs.cypress.io/api/commands/request#Yields
I need to prepare a Java test (citrus framework) which initial step is sending a http request. Unfortunately my app under tests does not reply to this http request with anything while my testing framework expects to have a response and generates an error otherwise. The best way to deal with such a situation which came to my mind is to use some kind of a proxy between my testing framework and actual application which will forward the test request to the actual application and reply back to the testing framework with OK status not waiting for the response from app.
Does it make a sense? How could I prepare such a proxy assuming that my tests are to be running with maven invocation?
I see following options:
Fire and forget: send the Http request (using the fork mode on the send operation in Citrus) and do not care for the response at all. Just leave out the receive message action to ignore the response in Citrus.
Expect the timeout: Send the Http request and use the receive timeout action to verify that the client does not receive a response in the given time
Assert/catch the timeout exception: Use the assert or catch action in Citrus to handle the timeout exception when sending the http request
Personally I would go for the option #2 where you send the Http request and verify that there is no response for a given amount of time. This makes sure that the actual behavior of your application to not send any response does not change over time.
UPDATED 9/16:
I've reworded my question. I'm trying to use cypress to test a work application that has an Angular frontend(http://localhost:4200) and a .NET Core backend (http://localhost:5000).
When the app starts, a login page loads with username and password fields. The cypress code test fills in the username and password and clicks the submit button, as show in my code below.
When the login (submit) button is clicked, it triggers a POST request to the .NET Core backend. The request submits the username and password and if the user is verified, a token comes back in response. The token is added to session storage and the user is logged in. It is this token value in the session storage that signifies the user is logged in. With the token added, the user gets redirected to the homepage.
Now the backend is NOT running. I need to simulate this POST request so the cypress test can get to the actual homepage.
I think I need to stub this POST request but I can't get this code to work. The request just aborts and looking at the console it says the request was not stubbed.
Here's my updated cypress code:
const username = 'johndoe';
const password = 'pass1234';
cy.server();
cy.get('h1').should('contain', 'Login');
cy.get('input[placeholder=Username]').type(username);
cy.get('input[placeholder=Password]').type(password);
cy.get('button[type=submit]').click();
cy.route({
method: 'POST',
url: 'http://localhost:5000/Authentication/Login',
response: {
access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' // some random characters
}
});
You might have more success if you set up the route before invoking the POST (clicking submit),
cy.server();
cy.route({
method: 'POST',
url: 'http://localhost:5000/Authentication/Login',
response: {
access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' // some random characters
}
});
...
cy.get('button[type=submit]').click();
However, if your app uses native fetch as opposed to XHR, core Cypress does not catch this type of request. You have to add a polyfill which essentially modifies the window.fetch method to allow cy.route() to succeed.
cypress.json
{
"experimentalFetchPolyfill": true
}
Please note the limitations section,
Limitations
The experimental polyfill is not foolproof. It does not work with fetch calls made from WebWorkers or ServiceWorker for example. It might not work for streaming responses and canceled XHRs. That's why we felt the opt-in experimental flag is the best path forward to avoid breaking already application under tests.
We are using the NextJS framework in which a request is being made using 'fetchJson'. But, the issue is that the XHR request is not being logged by cypress.
Below is the logs displayed in the 'Network' - the address request is being made and it received 200. The logic in the programming is that when I enter Pincode that matches length 6, the fetchJson method is being called which makes the request.
Here is my code for my test case:
it("Validate Error message for PinCode ",function(){
cy.server({
method: "POST"
});
cy.route("POST","/api/address").as("mypin");
cy.get("form").within(()=>{
cy.get("[name='clientStreetNo']").type("Street");
cy.get("[name='clientStreetName']").type("StreetName");
cy.get("[name='clientArea']").type("are");
cy.get("[name='clientPinCode']").type("500082");
Here is a screenshot of test execution; it shows address request being made, in the network tab.
I have tried multiple ways to make cypress log the xhr request but it done;t log. How can i make the request log?
I'm looking for a way in Chrome DevTools (or any equivalent) to control the HTTP requests done by my web application:
I want to approve HTTP requests before executing, or let them fail in an unexpected way (give it status 500 or something).
USAGE EXAMPLE: Testing unexpected behavior
Does anyone know a way to achieve this.
I see 2 possible solutions to achieve this goal on the client side:
Use the Request-Blocking panel from the Drawer (Open Chrome DevTools -> Esc -> '...' -> Request blocking
This is completely out-of-the-box and works for most "offline-first" use cases.
Use a service worker. They are basically a way to proxy requests and respond individually (e.g. by responding with a 500-er). You might want to enable/disable such a debugging-feature by using Chrome Devtools Snippets (Open Chrome DevTools -> Sources -> Snippets) as you don't want your requests to fail all the time :)
First you need to register your serviceworker like this:
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('/path-to-service-worker.js').then(function(registration) {
// registration successful
}).catch(function(err) {
// registration failed
});
}
Afterwards reload the browser (or install your service-worker in the DevTools -> Application -> Service Workers) so that your service-worker.js is active, may listen to the 'fetch' event and proxy the requests for this domain like this:
self.addEventListener('fetch', function(event) {
// this will set a breakpoint in chrome devtools, allowing you to manually edit the response
debugger;
// alternatively you could reponse with an error response like this:
event.respondWith(
new Response(null, {
status: 500
})
);
});
Sidenote: Due to security restrictions in the browser serviceworkers only work over https and on localhost.
Further information:
https://developer.mozilla.org/en-US/docs/Web/API/Response/Response
https://developers.google.com/web/fundamentals/primers/service-workers/
You can use Requestly Chrome extension to Redirect, Cancel, Block, Modify headers,... of the requests.
To approve requests before executing e.g. for AJAX requests create a redirect rule and point it to a static JSON file or another script.
To block a request use cancel request feature and set a custom pattern.