Giter VIP home page Giter VIP logo

py_fake_server's Introduction

py_fake_server

Build Status codecov Python versions

py_fake_server is a small Python library that gives you the ability to create high-level tests (functional tests) for your services/microservices without having to connect to real external http-services. It provides declarative API for both creating the server and for checking the expectation.

Table of contents:

Install

pip3 install py_fake_server

Getting Started

Here is a simple example showing how to create a dummy test with py_fake_server.

# dummy_test.py

import requests
from py_fake_server import FakeServer


def test_simple_example():
    server = FakeServer(host="localhost", port=8081)
    server.start()
    server.on_("get", "/hello"). \
        response(status=200, body="Hello, World!", content_type="text/plain")
    
    response = requests.get(server.base_uri + "/hello")
    
    assert server.was_requested("get", "/hello"). \
        exactly_once().check()
        

A more complex example

The one of the best example to show necessary and simplicity of this library is testing the API-Gateway service in isolation. Imagine, that our API-Gateway should check a user authentication in an auth microservice before update the user information in a portfolio microservice.

                       ┌───────────────┐ POST /auth             ┌──────────────┐
                       |               | <--------------------> | Auth-service |
PATCH /portfolios/34   |               |              HTTP 200  └──────────────┘
---------------------> |  API-Gateway  |
             HTTP 204  |               | PATCH /portfolios/34   ┌───────────────────┐           
                       |               | <--------------------> | Portfolio-service |
                       └───────────────┘              HTTP 204  └───────────────────┘

We don't want to up and run both auth and portfolio microservices, but we can use FakeServer instances instead.

                       ┌───────────────┐ POST /auth             ┌─────────────────────┐
                       |               | <--------------------> | FakeServer instance |
PATCH /portfolios/34   |               |              HTTP 200  └─────────────────────┘
---------------------> |  API-Gateway  |
             HTTP 204  |               | PATCH /portfolios/34   ┌─────────────────────┐           
                       |               | <--------------------> | FakeServer instance |
                       └───────────────┘              HTTP 204  └─────────────────────┘

Here is how it'll look in the code, using pytest as a testing framework.

# api_gateway_test.py

import pytest
import requests
from py_fake_server import FakeServer

API_GATEWAY_BASE_URI = "http://localhost:8080"


@pytest.fixture(scope="session")
def auth_server():
    server = FakeServer(host="localhost", port=8081)
    server.start()
    yield server
    server.stop()


@pytest.fixture(scope="session")
def portfolio_server():
    server = FakeServer(host="localhost", port=8082)
    server.start()
    yield server
    server.stop()


@pytest.fixture(scope="function", autouse=True)
def servers_cleanup(auth_server, portfolio_server):
    auth_server.clear()
    portfolio_server.clear()
    yield
    auth_server.clear()
    portfolio_server.clear()


def test_patch_portfolio_description(auth_server: FakeServer, portfolio_server: FakeServer):
    auth_server.on_("post", "/auth"). \
        response(status=200, json={"user_id": "34"})

    portfolio_server.on_("patch", "/portfolios/34"). \
        response(status=204)

    requests.patch(API_GATEWAY_BASE_URI + "/users/34/portfolio",
                   json={"description": "Brand new Description"},
                   cookies={"token": "auth-token-with-encrypted-user-id-34"})

    assert auth_server.was_requested("post", "/auth"). \
        exactly_once(). \
        for_the_first_time(). \
        with_cookies({"token": "auth-token-with-encrypted-user-id-34"}).check()

    assert portfolio_server.was_requested("patch", "/portfolios/34"). \
        exactly_once(). \
        for_the_first_time(). \
        with_query_params({"requested_user_id": "34"}). \
        with_json({"description": "Brand new Description"}). \
        with_content_type("application/json").check()

Documentation by example

Start server

server = FakeServer(host="localhost", port=8081)
server.start()

Stop server

server = FakeServer(host="localhost", port=8081)
server.start()
server.stop()

Create endpoint

Simple endpoint:

server.on_("get", "/some/path"). \
    response(status=200, body="Hello, World!", content_type="text/plain",
             headers={"Header Name": "Header Value"}, cookies={"Cookie Name": "Cookie Value"})

Specify number of responses:

server.on_("post", "/some/path/1"). \
    response(status=204).once()

server.on_("post", "/some/path/2"). \
    response(status=204).twice()

server.on_("post", "/some/path/3"). \
    response(status=204)._3_times()

server.on_("post", "/some/path/100"). \
    response(status=204)._100_times()

When requests endpoint more times than was specified:

import requests

server.on_("post", "/some/path/1"). \
    response(status=204).once()
 
response_1 = requests.post(server.base_uri + "/some/path/1")
response_2 = requests.post(server.base_uri + "/some/path/1")

response_1.status_code  # -> 204
response_2.status_code  # -> 500
response_2.text         # -> Server has not responses for [POST] http://localhost:8081/some/path/1

Specify chain of responses:

server.on_("post", "/some/path/1"). \
    response(status=204).once(). \
    then(). \
    response(status=401).once(). \
    then(). \
    response(status=204)

Clear created endpoints

server.clear()

Check expectations

Three interchangeable ways:

from py_fake_server import expect_that

# With an explicit call to the method "check()"
assert server.was_requested("get", "/some/path").check()
expect_that(server).was_requested("get", "/some/path").check()

# Without a call to the method "check()"
expect_that(server.was_requested("get", "/some/path"))

Expected number of requests:

assert server.was_requested("post", "/some/path/1"). \
    exactly_once().check()

assert server.was_requested("post", "/some/path/2"). \
    exactly_twice().check()
    
assert server.was_requested("post", "/some/path/3"). \
    exactly_3_times().check()

assert server.was_requested("post", "/some/path/100"). \
    exactly_100_times().check()

Endpoint is never called:

assert server.was_not_requested("post", "/never/called").check()

Specify the number of the check:

assert server.was_requested("patch", "/some/path/1"). \
    for_the_first_time(). \
    with_body("The first time body").check()

assert server.was_requested("patch", "/some/path/2"). \
    for_the_second_time(). \
    with_body("The second time body").check()

assert server.was_requested("patch", "/some/path/3"). \
    for_the_3_time(). \
    with_body("The third time body").check()

assert server.was_requested("patch", "/some/path/100"). \
    for_the_100_time(). \
    with_body("The 100th time body").check()

# Mix all together!
assert server.was_requested("patch", "/some/path"). \
    exactly_101_times(). \
    for_the_first_time(). \
    with_body("The first time body"). \
    for_the_second_time(). \
    with_body("The second time body"). \
    for_the_3_time(). \
    with_body("The third time body"). \
    for_the_100_time(). \
    with_body("The 100th time body").check()

Different types of the check:

assert server.was_requested("post", "some/path"). \
    exactly_5_times().\ 
    for_the_second_time(). \
    with_body("Expected string body"). \
    with_json({"Expected": "dictionary"}). \
    with_files({"Expected": b"file"}). \
    with_cookies({"Expected": "Cookie"}). \
    with_headers({"Containts": "This header"}). \
    with_content_type("String/Content-Type"). \
    with_query_params({"Expected": "Query parameter"}). \
    check()

License

MIT License

Copyright (c) 2017 Roman Telichkin

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

py_fake_server's People

Contributors

dependabot[bot] avatar telichkin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

py_fake_server's Issues

Query parameters support

Hi,

it seems the query parameters are not supported associated to a response. Example :

`
import requests
from py_fake_server import FakeServer

server = FakeServer(host="localhost", port=8081)
server.start()
server.on_("get", "/hello?toto=test").response(status=200, body="Hello, World!", content_type="text/plain")

response = requests.get(server.base_uri + "/hello")

assert server.was_requested("get", "/hello?toto=test").exactly_once().check()
`

The result :
AssertionError: Expect that server was requested with [GET] http://localhost:8081/hello?toto=test. 1 times.
But server was requested 0 times.

Did I miss something in the doc or is this function really missing?

Thanks,
Jean-Christophe

Project name sucks

py_fake_server is really hard name to write in code. It's long and have special characters. Also, there is no need to remind that this is python. You should consider to change it somehow.

My suggestions:

  • fake-server
  • hurry-server
  • hurry-fake

Support json as response's attributes

Dear Developer,

Please don't force me to import json on every stub:

Replace

    auth_server.on_("post", "/auth"). \
        response(status=200, body=json.dumps({"user_id": "34"}), content_type="application/json")

With

    auth_server.on_("post", "/auth"). \
        response(status=200, json={"user_id": "34"})

Unexpected split of expected query params

server.on_("get", "/bug").response(status=200)

requests.get(server.base_uri + "/bug", params={"query": "id=in=(1,2)|limit(1,0)"})

assert server.was_requested("get", "/bug"). \
    for_the_first_time(). \
    with_query_params({"query": "id=in=(1,2)|limit(1,0)"}).check()  # --> it raises AssertionError

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.