cjstehno / ersatz

🤖 A simulated HTTP server for testing client code with configurable responses.
https://cjstehno.github.io/ersatz
Apache License 2.0
47 stars 5 forks source link

Expect to see real world examples for post expectation with matcher #109

Closed havenqi closed 5 years ago

havenqi commented 5 years ago

Most of expecations are simple and easy to follow in guide doc. But when I'm solving practical problems, it's a hard time to figure out how. Like, I'm trying to write a POST expectation, the body content is XML, the response is to be sent only when specified XML tag values are met. Could you add such example code in the guide? (trying to implement an inbedded mock service with Ersatz in spock test framework for flexibility, instead of using the existing isolated Spring one) Thanks.

cjstehno commented 5 years ago

Can you provide an example in pseudo code?

havenqi commented 5 years ago

Below is the example, it has the whole xml sent , matched and received successfully. What if I just want the mock server match several tags and values of the XML before sending back the prepared response message. for example, when SignSN == 'abc' && MsgTp == '777' then mock returns another XML body. I think a matcher can do that within request body, but failed to work it out :(

class mockPlay extends Specification {

    @AutoCleanup('stop') private final ErsatzServer server = new ErsatzServer()
    private final OkHttpClient client = new OkHttpClient.Builder().build()

    def "Posting 2"() {
        setup:
        def origNode = createMessage()
        def recvString =XmlUtil.serialize(origNode)

        server.expectations {
            post('/posting') {
                body recvString, TEXT_XML
                decoder TEXT_XML, utf8String
                responder {
                    body recvString
                    encoder(TEXT_XML, XML) {
                        obj -> obj
                    }
                }
            }
        }.start()

        when:
        String value = exec(clientPost('/posting', 'text/xml; charset=utf-8', recvString).build()).body().string()

        def parsed = new XmlSlurper().parseText(recvString)
        println "check parsed=${parsed.root.MsgHeader.SndDt.text()}"

        then:
        value == recvString
    }

    private Builder clientPost(final String path, final String contentType, final String content) {
        new Builder().post(create(parse(contentType), content)).url("${server.httpUrl}${path}")
    }

    private Response exec(Request req) {
        client.newCall(req).execute()
    }

    static def createMessage = {
        new StreamingMarkupBuilder().bind {
            xmlBody {
                root(xmlns: "namespace_string") {
                    MsgHeader {
                        SndDt("2017-07-24T23:59:12")
                        MsgTp("e01.0x3.01")
                        IssrId("xxe44000010")
                        Drctn("11")
                        SignSN("4000283740")
                        NcrptnSN("4000880692")
                        DgtlEnvlp("cferWdfasfasdfqrfw52aaa")
                    }
                    MsgBody {
                        bodyField1("field1_Value")
                        bodyField2("field2_Value")
                    }
                }
            }
        }
    }
}
cjstehno commented 5 years ago

Yes, this would be done with a Hamcrest matcher - let me see if I can come up with an example.

Note: One thing I do notice in your code (from other issues you've posted as well), you are not setting the content-type in the response body. The configuration method that doesn't have a content-type argument exists for cases when you want to set it outside of the body call. In your case, this would mean that your encoder is not being called for the response. Not sure if this relates to the actual issue, but it is something you should be aware of.

cjstehno commented 5 years ago

Ok, Here is an example test where I use an XPath matcher to match against the contents of the XML in the request:

package com.stehno.ersatz.issues

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 all 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', utf8String
                body requestXml, 'text/xml; charset=utf-8'
                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
    }

    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()
    }
}

I have added this test to the codebase, and will document something similar as a "code recipe" in the user guide.

I will leave this issue open to track the documentation change.

(Please feel free to suggest other use case documentation items in other tickets)

havenqi commented 5 years ago

Thanks! Just have time and get back to this update. With your example code, I tried another way to leverage the easier solution provided by Groovy to process Xml element. See #113

cjstehno commented 5 years ago

Added to user guide.