pact-foundation / pact-specification

Describes the pact format and verification specifications
MIT License
295 stars 28 forks source link

Add state transition for interactions #67

Open eeelin opened 4 years ago

eeelin commented 4 years ago

consumer code,


def get_token(username):
     rsp = requests.get("/user/%s/token" % username)
     if rsp.json()["data"]:
        return rsp.json()["data"][0]["token"]
    rsp = requests.post("/user/%s/token" % username)
    return rsp.json()["token"]

def list_tokens(username):
     rsp = requests.get("/user/%s/token" % username)
     for it in rsp.json()["data"]:
         yield it["token"]

test code,


def test_get_token_auto_create():
    pact = Consumer('foo').has_pact_with(Provider("token"))
    with (pact
       .given("user foo has no token")
       .upon_receiving("query token of user foo")
       .with_request("GET", "/user/foo/token")
       .will_respond_with(200, body=json.dumps({
                    "data": [ ]
                }))

       .given("user foo has no token")
       .upon_receiving("create token for user foo")
       .with_request("POST", "/user/foo/token")
       .will_respond_with(200, body=json.dumps({
                    "token": "xxx"
                }))

       .given("user foo has a token")
       .upon_receiving("query token for user foo")
       .with_request("POST", "/user/foo/token")
       .will_respond_with(200, body=json.dumps({
                     "data": [ {"data": "xx" }]
                }))
         ):

         ):
         token = get_token("foo")
         tokens = list_tokens("foo")
         assert len(tokens) == 1 and token in tokens

the test above is not valid, because there two GET requests match path "/user/foo/tokens".

To fix it, maybe we need make mock service stateful. The state of service can be changed on specific match interaction, just like presudo code below,


def test_get_token_auto_create():
    pact = Consumer('foo').has_pact_with(Provider("token"))
    with (pact
       .given("user foo has no token") # first state as initial state implicitly
       .upon_receiving("query token of user foo")
       .with_request("GET", "/user/foo/token")
       .will_respond_with(200, body=json.dumps({
                    "data": [ ]
                }))

       .given("user foo has no token")
       .upon_receiving("create token for user foo")
       .with_request("POST", "/user/foo/token")
       .will_respond_with(200, body=json.dumps({
                    "token": "xxx"
                }))
       .transition_to("user foo has a token")  # <----------------------- HERE !

       .given("user foo has a token")
       .upon_receiving("query token for user foo")
       .with_request("POST", "/user/foo/token")
       .will_respond_with(200, body=json.dumps({
                     "data": [ {"data": "xx" }]
                }))
         ):

         ):
         token = get_token("foo")
         tokens = list_tokens("foo")
         assert len(tokens) == 1 and token in tokens
bethesque commented 4 years ago

To fix it, maybe we need make mock service stateful

The mock service is stateful. You need to test those two scenarios in separate tests. Please read https://github.com/pact-foundation/pact-mock_service#mock-service-usage