Skip to content

Commit

Permalink
Allow port specification (#115)
Browse files Browse the repository at this point in the history
* Adds support for specifying the server port on startup
* Add a bit more documentation
  • Loading branch information
cjstehno committed Dec 7, 2018
1 parent 6a89102 commit a7452fc
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 2 deletions.
81 changes: 81 additions & 0 deletions src/docs/asciidoc/index.adoc
Expand Up @@ -25,6 +25,12 @@ allows two main forms, a Java-style chained builder, and a Groovy DSL, both of w
Ersatz is developed with testing in mind. It does not favor any specific testing framework, but it does work well with both the JUnit and Spock
frameworks.

== What's New in 1.9

* Corrections to the closure variable scoping.
* Support for configuring the server port - though, in general, this is not recommended.
* Some added usage documentation

== What's New in 1.8

* Variable scope changes - the configuration Groovy DSL closures had incorrect (or inadequate) resolution strategies specified which caused variables to be resolved incorrectly in some situations. All of the closures now use `DELEGATE_FIRST`; however, beware this may cause some issues with existing code.
Expand Down Expand Up @@ -1371,3 +1377,78 @@ println( http.newCall(request).execute().body().string() )
----

which will print out "Hello Kotlin!" when executed.

=== Matching XML Body Content

A unit test has been added to provide a better example of how to use the Hamcrest matchers in a request. See the `BodyContentMatcherSpec` test class in the source code, but
a summary is provided below:

[source,groovy]
----
import com.stehno.ersatz.DecodingContext
import com.stehno.ersatz.ErsatzServer
import com.stehno.ersatz.util.HttpClient
import okhttp3.MediaType
import okhttp3.Response
import spock.lang.AutoCleanup
import spock.lang.Specification
import javax.xml.parsers.DocumentBuilderFactory
import static com.stehno.ersatz.ContentType.TEXT_XML
import static com.stehno.ersatz.Decoders.utf8String
import static com.stehno.ersatz.Encoders.text
import static okhttp3.RequestBody.create
import static org.hamcrest.CoreMatchers.equalTo
import static org.hamcrest.xml.HasXPath.hasXPath
class BodyContentMatcherSpec extends Specification {
@AutoCleanup private final ErsatzServer server = new ErsatzServer()
private final HttpClient http = new HttpClient()
void 'matching part of body content'() {
setup:
String requestXml = '<request><node foo="bar"/></request>'
String responseXml = '<response>OK</response>'
server.expectations {
post('/posting') {
decoder('text/xml; charset=utf-8') { byte[] bytes, DecodingContext ctx ->
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(bytes))
}
body hasXPath('string(//request/node/@foo)', equalTo('bar')), 'text/xml; charset=utf-8'
called 1
responder {
body responseXml, TEXT_XML
encoder TEXT_XML, String, text
}
}
}
when:
Response response = http.post(server.httpUrl('/posting'), create(MediaType.get('text/xml; charset=utf-8'), requestXml))
then:
response.body().string() == responseXml
when:
response = http.post(server.httpUrl('/posting'), create(MediaType.get('text/xml; charset=utf-8'), '<request><node foo="blah"/></request>'))
then:
response.code() == 404
and:
server.verify()
}
}
----

This test sets up a POST expectation with the XML request body content being used as one of the matching criteria. Hamcrest provides an XPath-based
matcher, `hasXPath(String, Matcher)`, which works well here. A custom XML-decoder was installed to parse the request into the XML document format
required by the matcher.

The test shows to requests made to the server, one with the expected content and one without - the results verify that only the correct call was
actually matched.

See the http://hamcrest.org/JavaHamcrest/[Hamcrest] documentation for more details about pre-existing and custom `Matcher`s.
18 changes: 16 additions & 2 deletions src/main/groovy/com/stehno/ersatz/ErsatzServer.groovy
Expand Up @@ -101,6 +101,8 @@ class ErsatzServer implements ServerConfig, Closeable {
private boolean mismatchToConsole
private URL keystoreLocation
private String keystorePass = 'ersatz'
private int desiredHttpPort = EPHEMERAL_PORT
private int desiredHttpsPort = EPHEMERAL_PORT
private int actualHttpPort = UNSPECIFIED_PORT
private int actualHttpsPort = UNSPECIFIED_PORT
private AuthenticationConfig authenticationConfig
Expand Down Expand Up @@ -399,18 +401,30 @@ class ErsatzServer implements ServerConfig, Closeable {
return this
}

@Override
ServerConfig httpPort(int serverPort) {
desiredHttpPort = serverPort
return this
}

@Override
ServerConfig httpsPort(int serverPort) {
desiredHttpsPort = serverPort
return this
}

/**
* Used to start the HTTP server for test interactions. This method should be called after configuration of expectations and before the test
* interactions are executed against the server.
*/
@SuppressWarnings(['Println', 'DuplicateNumberLiteral'])
void start() {
if (!started) {
Undertow.Builder builder = Undertow.builder().addHttpListener(EPHEMERAL_PORT, LOCALHOST)
Undertow.Builder builder = Undertow.builder().addHttpListener(desiredHttpPort, LOCALHOST)
timeoutConfig.call(builder)

if (httpsEnabled) {
builder.addHttpsListener(EPHEMERAL_PORT, LOCALHOST, sslContext())
builder.addHttpsListener(desiredHttpsPort, LOCALHOST, sslContext())
}

BlockingHandler blockingHandler = new BlockingHandler(new EncodingHandler(
Expand Down
18 changes: 18 additions & 0 deletions src/main/groovy/com/stehno/ersatz/ServerConfig.groovy
Expand Up @@ -203,4 +203,22 @@ interface ServerConfig {
* @return a reference to this server configuration
*/
ServerConfig authentication(Consumer<AuthenticationConfig> config)

/**
* Allows the specific configuration of the HTTP server port. The default ephemeral port should be used in most cases since
* specifying the port will negate the ability to run tests in parallel and will also allow possible collisions with
* other running servers on the host.
*
* Do NOT specify this setting unless you really need to.
*/
ServerConfig httpPort(int value)

/**
* Allows the specific configuration of the HTTPS server port. The default ephemeral port should be used in most cases since
* specifying the port will negate the ability to run tests in parallel and will also allow possible collisions with
* other running servers on the host.
*
* Do NOT specify this setting unless you really need to.
*/
ServerConfig httpsPort(int value)
}
41 changes: 41 additions & 0 deletions src/test/groovy/com/stehno/ersatz/ErsatzServerPortsSpec.groovy
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2018 Christopher J. Stehno
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.stehno.ersatz

import com.stehno.ersatz.util.HttpClient
import spock.lang.AutoCleanup
import spock.lang.Specification

class ErsatzServerPortsSpec extends Specification {

// NOTE: if this test starts failing for odd reasons, add some logic to ensure port is available

@AutoCleanup private final ErsatzServer ersatz = new ErsatzServer({
httpPort 8675
expectations {
get('/hi').responds().code(200)
}
})
private final HttpClient http = new HttpClient()

void 'running with explicit port'(){
expect:
http.get(ersatz.httpUrl('/hi')).code() == 200

and:
ersatz.httpPort == 8675
}
}

0 comments on commit a7452fc

Please sign in to comment.