Baz
Baz

Reputation: 13135

Test Environment with Mocked REST API

Lets say I have a very simple web app which is presented as blue if the current president is a democrat and red if they are a republican. A REST API is used to get the current president, via the endpoint:

/presidents/current

which currently returns the json object:

{name: "Donald Trump", party: "Republican"}

So when my page loads I call the endpoint and I show red or blue depending on who is returned.

I wish to test this HTML/javascript page and I wish to mock the back-end so that I can control from within the test environment the API responses. For example:

def test_republican():
    # configure the response for this test that the web app will receive when it connects to this endpoint
    configure_endpoint(
        "/presidents/current", 
        jsonify(
            name="Donald Trump",
            party="Republican"
        )
    )  

    # start the web app in the browser using selenium 
    load_web_app(driver, "http://localhost:8080")  

    e = driver.find_element_by_name("background")
    assert(e.getCssValue("background-color") == "red")


def test_democrat():
    # configure the response for this test that the web app will receive when it connects to this endpoint
    configure_endpoint(
        "/presidents/current", 
        jsonify(
            name="Barack Obama",
            party="Democrat"
        )
    )    

    # start the web app in the browser using selenium 
    load_web_app(driver, "http://localhost:8080")  

    e = driver.find_element_by_name("background")
    assert(e.getCssValue("background-color") == "blue")

So the question is how should I implement the function configure_endpoint() and what libraries can you recommend me?

Upvotes: 8

Views: 1697

Answers (4)

asleepysamurai
asleepysamurai

Reputation: 1372

For those who stumble upon this question, and do not want to end up writing the code to create their own mock server implementations of the API, you can use Mocktastic, which is a downloadable desktop application for Windows, MacOS and Linux, which provides an easy to use GUI to setup your mock API servers.

Upvotes: 0

Vladimir Ignatev
Vladimir Ignatev

Reputation: 2176

As @Kie mentioned, configure_endpoint implementation won't be enough, if you're going to stub the whole server-side within Selenium Python code. You would need a web server or whatever that will response via HTTP to requests from within testing environment.

It looks like the question is partially about testing of client-side code. What I see is that you're trying to make unit-test for client-side logic, but use integration testing suite in order to check this logic (it's strange).

The main idea is as follows.

You're trying to test client-side code. So, let's make mocks client-side too! Because this part of code is completely client-side related stuff.

If you actually want to have mocks, not stubs (watch the difference here: https://stackoverflow.com/a/3459491/882187) it is a better way to mock out HTTP requests inside your Javascript code. Just because you're testing a client-side piece of code, not some parts of server-side logic.

Having it isolated from whatever server-side is - is a great idea that you would love when your project become grow, while more and more endpoints will be appearing.

For example, you can use the following approach:

var restResponder = function() { // the original responder your client-side app will use
  this.getCurrentPresident = function(successCallback) {
    $.get('/presidents/current', callback);
  }
};

var createMockResponder = function(president, party){ // factory that creates mocks
  var myPresident = president;
  var myParty = party;

  return function() {
    this.getCurrentPresident = function (successCallback) {
      successCallback({"name": myPresident, "party": myParty});
    }
  };
}

// somewhere swap the original restResponder with new mockResponder created by 'createMockResponder'

// then use it in your app:

function drawColor(restResponder, backgroundEl) {
  restResponder.getCurrentPresident(function(data){
     if (data.party == "Democrat") $(backgroundEl).style('background-color', 'blue')
     else if (data.party == "Republican") $(backgroundEl).style('background-color', 'red')
     else console.info('Some strange response from server... Nevermind...');
  });
}

Practically, this implementation depends on what do you have at the client-side as a framework. If jQuery, then my example is enough, but it looks very wordy. In case you have something more advanced, like AngularJS, you can do the same in 2-3 lines of code:

// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
authRequestHandler = $httpBackend.when('GET', '/auth.py')
                                 .respond({userId: 'userX'}, {'A-Token': 'xxx'});

Check out the docs: https://docs.angularjs.org/api/ngMock/service/$httpBackend

If you're still stick to the idea, that you need mocks inside Selenium tests, please try this project: https://turq.readthedocs.io/en/latest/

It serves with Python DSL for describing REST responders. Using turq your mocks will look as follows:

path('/presidents/current').json({'name':'Barack Obama', 'party': 'Democrat'}, jsonp=False)

Also, I would recommend to try stubs instead of mocks and use this Python module: mock-server https://pypi.python.org/pypi/mock-server/0.3.7 You are required to create the directory layout containing corresponding pre-populated JSON responses and to add some boilerplate code in order to make the mock-server respond on 'localhost:8080'. The directory layout for your example will look like this:

stub_obama/
  presidents/
    current/
      GET_200.json      # will contain {"name": "Barack Obama", "party": "Democrat"}
stub_trump/
  presidents/
    current/
      GET_200.json      # will contain {"name": "Donald Trump", "party": "Republican"}

But the mock_server is based on Tornado, it is very heavy solution for using in tests I think.

I hope, my answer is helpful and informative. Welcome to discuss it! I made tons of projects with Selenium, big and small tests, tested client-side and server-side.

Upvotes: 4

kiecodes
kiecodes

Reputation: 1659

I would use tornado web framework.

import json
import functools
import operator
from tornado import ioloop, web, gen
from tornado.options import define, options

define("data_file", default='default/mock.json', type=str)

class Handler(web.RequestHandler):

    def data_received(self, chunk):
        pass

    def initialize(self, data):
        self.data = data

    @gen.coroutine
    def get(self, *args, **kwargs):
        path = self.request.path.split("/")[1:]
        path = functools.reduce(
            operator.add,
            [[k, v[0].decode("utf-8")] for k, v in         self.request.query_arguments.items()],
            path
        )

        try:
            self.write(functools.reduce(operator.getitem, path, self.data))
        except KeyError:
            self.set_status(404)


class Application(web.Application):
    def __init__(self):
        data = {}
        with open(options.data_file) as data_file:
            data = json.load(data_file)

        handlers = [
            ('(.*)', Handler, {"data": data})
        ]
        settings = dict(
            gzip=True,
            static_hash_cache=True,
        )
        web.Application.__init__(self, handlers, **settings)


    def main():
        io_loop = ioloop.IOLoop.instance()
        backend_application = Application()
        backend_application.listen(8001)
        io_loop.start()

    if __name__ == "__main__":
        main()

This is a code I used for mocking a REST-API which is a standalone script, but it can be embedded into your test environment as well.

I defined a JSON file which defines the different path components and what should be returned. Like this:

{
    "presidents": {
        "current": {
            "name": "Donald Trump", 
            "party": "Republican"
        }
    }
}

I saved this to a mock.json and called the script with a parameter mock_rest.py --data-file="./mock.json".

I hope that gives you a starting point and a good example.

Upvotes: 2

das-g
das-g

Reputation: 9994

If your load_web_app function uses the requests library to access the REST API, using requests-mock is a convenient way to fake that library's functionality for test purposes.

Upvotes: 1

Related Questions