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.
0 comments:
Post a Comment