Sunday, February 5, 2017

Test Environment with Mocked REST API

Leave a Comment

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?

3 Answers

Answers 1

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: http://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. If 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 just need to create the directory layout containing corresponding pre-populated JSON responses and 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 was helpful or at least informative. Welcome to discuss it! I've seen and made a ton of projects with Selenium, big and small tests, tested client-side and server-side.

Answers 2

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.

Answers 3

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment