I have this test that launches a service at port 7000 and inside the only endpoint I do a failing assertion:
#Test
fun `javalin assertion should fail`() {
Javalin.create()
.get("/") { assertTrue(false) }
.start()
newHttpClient().send(
HttpRequest.newBuilder()
.uri(URI.create("http://localhost:7000/"))
.GET().build(),
discarding()
)
}
The problem is that the test always passes (but it should fail):
(same behavior happens by running ./gradlew test)
... even though there's a console output claiming that a test failed:
[Test worker] INFO io.javalin.Javalin - Listening on http://localhost:7000/
[Test worker] INFO io.javalin.Javalin - Javalin started in 356ms \o/
[qtp2100106358-22] ERROR io.javalin.Javalin - Exception occurred while servicing http-request
org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
..
Probably, it's being run in another thread, but I wonder if there's a way to attach it to the same context.
(Weirdly, in another scenario in my app - that I couldn't isolate - it properly fails.)
TLDR
To make the test fail as you'd expect, add an assertion on the response you get from your Javalin instance.
#Test
fun `javalin assertion should fail`() {
Javalin.create()
.get("/") { assertTrue(false) } // or any expression that throws an Exception, like Kotlin's TODO()
.start()
val javalinResponse: HttpResponse<Void> = newHttpClient().send(
HttpRequest.newBuilder()
.uri(URI.create("http://localhost:7000/"))
.GET().build(),
discarding()
)
assertThat(javalinResponse.statusCode()).isEqualTo(200) // will fail with Expected: 200, Actual: 500
}
Details
There're two distinct steps in the test: new Javalin instance configuration + build, and calling that instance with a HttpClient.
In Javalin configuration + build step it gets instructed to do assertTrue(false) when called on the / endpoint. assertTrue(false) will throw an AssertionFailedError, but the behavior will be the same if you throw something else there. Now, as many (all?) other webservers, Javalin / Jetty will try to catch any uncaught exceptions that happen within it and return a HTTP response with code 500 (Internal Server Error).
Indeed, this all happens in another thread, as internally a Jetty webserver instance is being launched, which does the port listening, HTTP request / response handling and other important stuff.
So when later in the test an HTTP call is performed to the new Javalin instance, it gets the 500 (Internal Server Error) response successfully, and as originally there are no assertions on the response and there were no uncaught exceptions, the test is deemed successful.
Never do assertions inside the Javalin handler, because if the test fails, the JUnit exception is swallowed by Javalin and the test fails silently (better explained in the other answer). The solution is to make an assertion outside, in the end, as in the Arrange, Act, Assert pattern.
How? You store what you want to assert inside the handler and assert it later. For example, if it's a POST.
var postedBody: String? = null
fakeProfileApi = Javalin.create().post("profile") {
postedBody = it.body()
}.start(1234)
val profileGateway = ProfileGateway(apiUrl = "http://localhost:1234")
profileGateway.saveProfile( // contains the HTTP POST
Profile(id = "abc", email = "john.doe#gmail.com".toEmail())
)
JSONAssert.assertEquals(
""" { "id": "abc", "email": "johndoe#gmail.com" } """,
postedBody, true
)
If it's a GET, it's easier:
fakeProfileApi = Javalin.create().get("profile/abc") {
it.result(""" {"id": "abc", "email": "johndoe#gmail.com"} """)
}.start(1234)
val profileGateway = ProfileGateway(apiUrl = "http://localhost:1234")
val result = profileGateway.fetchProfile("abc") // contains the HTTP GET
assertEquals(
Profile(id = "abc", email = "john.doe#gmail.com".toEmail()),
result
)
More info: Unit testing a gateway with Javalin
Related
I am launching android test on Saucelab, however I can see the script is launching the mobile app on saucelab but not able to perform any action on it and throwing this exception:
12:54:20.931 [main] ERROR com.intuit.karate - java.net.SocketTimeoutException: Read timed out, http call failed after 32303 milliseconds for url: https://oauth-abdulkadir786-684f1:1bd00f8e-392f-4b4c-8f0e-2597cc9912a3#ondemand.eu-central-1.saucelabs.com:443/wd/hub/session
I am using the following steps for execute my test:
Configure driver in karate-config.js
var android = {}
android["desiredConfig"] = {
"accessKey":"1bd00f8e-392f-4b4c-8f0e-2597cc9912a3",
"deviceName":"Android GoogleAPI Emulator",
"app" : "storage:b82d6099-60ad-49dd-b15f-925166e03dcd",
"platformVersion" : "11.0",
"platformName" : "Android",
"newCommandTimeout":300,
"automationName" : "UiAutomator2",
"username": "oauth-abdulkadir786-684f1"
}
config["android"] = android
Then Writing the feature file to call the webDriverSession:
Feature: Calling test from Sauce labs
Background:
* configure driver = { type: 'android', start: false, webDriverUrl: 'https://oauth-abdulkadir786-684f1:1bd00f8e-392f-4b4c-8f0e-2597cc9912a3#ondemand.eu-central-1.saucelabs.com:443/wd/hub' }
Scenario: android mobile app UI tests
Given driver { webDriverSession: { desiredCapabilities : "#(android.desiredConfig)"} }
* delay(2000)
Then click('/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout[2]/android.widget.Button[3]')
Any help would be highly appreciated.....
It looks like Karate is timing out the session before the emulator and app gets a chance to load fully:
Read timed out, http call failed after 32303 milliseconds for url: https://oauth-abdulkadir786-684f1:1bd00f8e-392f-4b4c-8f0e-2597cc9912a3#ondemand.eu-central-1.saucelabs.com:443/wd/hub/session
The suspicious parts are Read timed out, 32303 milliseconds (which is pretty close to 30 seconds and so is probably a config thing; 30 or 60 seconds is a common default timeout) and the path /wd/hub/session looks like the initial POST request which starts a sesson.
You'll probably have more luck if you increase the connectTimeout and readTimeout, either in your Background or in karate-config.js:
karate.configure('connectTimeout', 60000);
karate.configure('readTimeout', 60000);
I have a requirement in Nifi where I have cycle through different HTTPS REST Endpoints and provide different certificates for some endpoints and different username / password for some other endpoints.
I used InvokeHTTP processor to send the requests, although URL takes an expression language, I cannot setup SSLContextService with an expression.
Alternatively, I thought on using ExecuteScript to call those Endpoints, however as listed here in StackOverflow post; I still don't know how to programmatically call an external service through a script.
Any help appreciated.
just for fun created the groovy script that calls http.
for sure you can avoid using it. and I believe InvokeHTTP processor covers almost all needs.
However.. going to call test rest service: /post at https://httpbin.org
the flow: GenerateFlowFile (generates body) -> EcecuteGroovyScript (call service)
The body generated by GenerateFlowFile : {"id":123, "txt":"aaabbbccc"}
In ExecuteGroovyScript 1.5.0 declare the CTL.ssl1 property and link it to StandardSSLContextService
and now the script:
#Grab(group='acme.groovy', module='acmehttp', version='20180301', transitive=false)
import groovyx.acme.net.AcmeHTTP
import org.apache.nifi.ssl.SSLContextService.ClientAuth
def ff=session.get()
if(!ff)return
def http
ff.write{ffIn, ffOut->
http = AcmeHTTP.post(
url: "https://httpbin.org/post", //base url
query: [aaa:"hello", bbb:"world!"], //query parameters
// send flowfile content (stream) as a body
body: ffIn,
headers:[
//assign content-type from flowfile `mime.type` attribute
"content-type":ff.'mime.type'
],
// you can declare `CTX.ssl1`, `CTX,.ssl2`,... processor properties and map them to SSLContextService
// then depending on some condition create different SSLContext
// in this case let's take `CTL.ssl1` service to create context
ssl: CTL["ssl"+1].createSSLContext(ClientAuth.WANT),
// the next commented line creates trust all ssl context:
//ssl: AcmeHTTP.getNaiveSSLContext(),
// the receiver that transfers url response stream to flowfile stream
receiver:{respStream, httpCtx-> ffOut << respStream }
)
}
//set response hesders as flow file attributes with 'http.header.' prefix
http.response.headers.each{ k,v-> ff['http.header.'+k]=v }
//status code and message
ff.'http.status.code' = http.response.code
ff.'http.status.message' = http.response.message
if( http.response.code < 400){
//transfer to success if response was ok
REL_SUCCESS << ff
}else{
//transfer to failure when response code is 400+
REL_FAILURE << ff
}
Akka HTTP client requests return Future[HttpResponse] - how should one handle the Future failing? Just log an error or re-throw it to the supervisor?
Is there documentation of the type of errors that can be returned by thrown by the client (and hence automatically propagated to the supervisor ) as well as errors that can cause the Furure to fail.
It's matter of taste mostly. I typically convert Future[HttpResponse] to Future[Try[HttpResponse]] and then handle it as
response.flatMap { tryResp =>
tryResp match {
case Success(res) =>
res.status match {
case OK =>
// Unmarshal response here into Future[Something]
case Found =>
// Handle redirect by calling requestBlhBlah() again with anotehr URI
case _ =>
// I got status code I didn't expect so I wrap it along with body into Future failure
Unmarshal(res.entity).to[String].flatMap { body =>
Future.failed(new IOException(s"The response status is ${res.status} [${request.uri}] and response body is $body"))
}
}
case Failure(ex) =>
Future.failed(ex)
}
}
If you're using flow-based client you can also specify Decider to handle errors
val decider: Decider = {
case ex =>
ex.printStackTrace()
Supervision.Stop // Passes error down to subscriber
}
and then use it in either materializer
implicit val materializer = ActorMaterializer(ActorMaterializerSettings(system).withSupervisionStrategy(decider))(system)
or in per-flow basis via .withAttributes(ActorAttributes.supervisionStrategy(decider))
As per Future failure it's up to you how to handle it. You can convert failure to something else using recoverWith or log it in Future.onFailure.
Can we just catch the adapter calling failure error details? sample code :
var invocationData = {
adapter : 'UploadAdapter',
procedure : 'uploadImage',
parameters : [uuid, base64Str]
};
WL.Client.invokeProcedure(invocationData, {
onSuccess : uploadImageSuccess,
onFailure : uploadImageFail,
timeout : 60000,
});
I've already tried this:
function uploadImageFail(result){
WL.Logger.debug(JSON.stringify(result);
}
But it was just:
{"invocationContext":null}
My scenario : I'm working on an image uploading adapter (via cordova), and sometimes the uploading may fail. I can easily catch the error message returned from the backend service (which is handled in the uploadImageSuccess function), but it's not easy to retrieve the error logs when the invoking procedure goes wrong.
I am not sure you need to JSON.stringify it the result object. Instead you may want to simply use result.errorMsg.
I also googled, and it produced results on the code that needs to be used... so I would try the options provided in the following articles:
https://www.ibm.com/developerworks/community/blogs/worklight/entry/handling_backend_responses_in_adapters?lang=en
http://www.ibm.com/developerworks/websphere/techjournal/1212_paris/1212_paris.html
I am running into some timing issues with some mocha-cakes test scripts that run against restify services. I'm using the Restify JSON client to issue the calls, which use callbacks rather than promises. I've passed in the done function to my Givens and Whens, so that I can perform the necessary blocking against these async calls, which prevents inconsistent test suite runs (without the dones, it's a tossup which and how many Thens and Ands will pass).
I am moderately skilled with coffescript, and only a novice when it comes to mocha/mocha-cakes, so I am most certainly doing something wrong in my code. Here is an example of a couple of the test cases that are failing:
require 'mocha-cakes'
should = require 'should'
restify = require 'restify'
Feature "Account API",
"In order to have control over structured Account documents",
"as a consumer of investment account information,",
"I need a RESTful service API.", ->
Scenario "GET /account/:userid", ->
client = restify.createJSONClient
url: "http://localhost:8080",
version: "*"
_e1 = null
_r1 = null
_e2 = null
_r2 = null
_d2 = null
# GET non-existent account
Given "I have not yet created the Account", ->
When "I request the Account", (done) ->
client.get "/account/99", (err, req, res, obj) ->
_e1 = err
_r1 = res
done()
err
Then "it should respond with an error", ->
_e1.should.be.ok
And "the status code should be 404", ->
_r1.should.have.status 404
# GET existent account
Given "I have created the Account", (done) ->
client.post "/account", { userId: 1, accountType: 0, accountCategories: [], beneficiaries: [], accountOwner: { firstName: "Test", lastName: "User" } }, (err) ->
done()
err
When "I request the Account", (done) ->
client.get "/account/1", (err, req, res, obj) ->
_e2 = err
_r2 = res
_d2 = obj
done()
err
Then "it should responond with a document", ->
_d2.should.be.ok
And "it should have the userId 1", ->
_d2.userId.should.eql 1
And "it should have an accountOwner property", ->
_d2.accountOwner.should.be.ok
And "the status code should be 200", ->
_r2.should.have.status 200
When I run this, my output is always the following:
c:\Development\Clients\Pensco\AngularJS\Pensco\newaccountwizard.api>mocha
test/AccountAPITests.coffee -r should -R spec --compilers
coffee:coffee-script/register
Feature: Account API
In order to have control over structured Account documents
as a consumer of investment account information,
I need a RESTful service API.
Scenario: GET /account/:userid
◦
- ◊ Given: I have not yet created the Account (pending)
◦
1) When: I request the Account
◦
√ Then: it should respond with an error
◦
√ And: the status code should be 404
◦
2) Given: I have created the Account
◦
3) When: I request the Account
◦
√ Then: it should responond with a document
◦
√ And: it should have the userId 1
◦
√ And: it should have an accountOwner property
◦
√ And: the status code should be 200
6 passing (6s) 1 pending 3 failing
1) Feature: Account API
In order to have control over structured Account documents
as a consumer of investment account information,
I need a RESTful service API.
Scenario: GET /account/:userid ◦ When: I request the Account:
Error: timeout of 2000ms exceeded
at [object Object].<anonymous> (C:\Users\Jon\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:139:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
2) Feature: Account API
In order to have control over structured Account documents
as a consumer of investment account information,
I need a RESTful service API.
Scenario: GET /account/:userid ◦ Given: I have created the Account:
Error: timeout of 2000ms exceeded
at [object Object].<anonymous> (C:\Users\Jon\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:139:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
3) Feature: Account API
In order to have control over structured Account documents
as a consumer of investment account information,
I need a RESTful service API.
Scenario: GET /account/:userid ◦ When: I request the Account:
Error: timeout of 2000ms exceeded
at [object Object].<anonymous> (C:\Users\Jon\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:139:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
Now, I know that my REST calls via client.get/client.post occur almost instantaneously. When I remove the dones, and run without them, with the exception of the first run after restarting my restify service server, usually only the first or second Then/And fails, the rest succeed. There is a timing issue of maybe a few milliseconds, but definitely not 2000ms. I'm curious why my Givens and Whens suddenly start timing out when I throw in the done() calls.
I am pretty sure I am misunderstanding how mocha-cakes is transforming the coffescript Feature->Scenario->Given/When->Then/And/... into describe/it calls. I suspect that somehow the scope within which done applies is larger than it would seem to be given the nature of mocha-cakes script structure...I'm just not sure exactly what that scope is.
I am also not familiar with mocha-cakes. I am using mocha/(lit)coffee to test restify. I have found it convenient to wrap my calls in promises, as latest mocha is promise-aware. Then I don't have to bother with "done". Also note you may need to call res.end() or res.resume() (See this explanation)
For just "GET":
Promise = require('bluebird') # or whatever, I presume
port = 8776 # ditto
getHttpJson = (addr)->
addr = normalizeUrl(addr)
new Promise ( resolve, reject )->
req = http.get(addr, _completeResponse(resolve) )
.on( 'error', reject )
req.end()
General case:
requestHttpJson = (method, addr, data)->
if data?
data = JSON.stringify(data)
urlBits = getUrlBits(addr)
new Promise (resolve, reject)->
req = http.request(
method: method
headers: {
"Content-Type": "application/json" }
hostname: urlBits.hostname
port: urlBits.port
path: urlBits.pathname
, _completeResponse(resolve) )
req.on( 'error', reject )
if data?
req.write( data )
req.end()
postHttpJson = (addr, data)->
requestHttpJson('POST', addr, data)
putHttpJson = (addr, data)->
requestHttpJson('PUT', addr, data)
deleteHttpJson = (addr, data)->
requestHttpJson('DELETE', addr, data)
Break down address into components and add defaults. ("port" is a module global.)
getUrlBits = (addr)->
bits = url.parse(addr)
bits.port = bits.port || port
bits.hostname = bits.hostname || 'localhost'
bits.protocol = bits.protocol || 'http'
return bits
normalizeUrl = (addr)->
url.format(getUrlBits(addr))
Utility to parse body of request & resolve.
_completeResponse = (resolve)->
(res)->
body = []
res.on 'data', (data)->
body.push data
res.on 'end', ->
body = body.join ''
content = if body == '' then null else JSON.parse(body)
resolve([res,content])