Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quest: Add Request Recording #172

Open
5 tasks
trek opened this issue Oct 4, 2016 · 3 comments
Open
5 tasks

Quest: Add Request Recording #172

trek opened this issue Oct 4, 2016 · 3 comments

Comments

@trek
Copy link
Member

trek commented Oct 4, 2016

Currently Pretender works in two basic and complimentary ways: request stubbing and request proxying. We'd like to add a third, chimeric, methodology: request recording. In this mode the first time a particular verb/path pairing is accessed we'd like to proxy to a real world source of data, store this data in some location, then return the data. Subsequent requests will used the stored version of the data.

The current use case for this feature is performance regression testing JavaScript libraries/frameworks inside real world applications. Performance regression testing involves taking a real application and running a series of operations many hundreds or thousands of times.

A problem arises: if you're making real world requests, it is difficult to isolate the performance characteristics of the UI from the performance characteristics of the server. Stubbing can solve this; however, manually stubbing of large applications can be cumbersome.

Defining Success

Successful PRs on the road to this feature include:

Understanding Pretender Internals

Pretender is a fairly small library, but knowing how it works will help you in this quest.

How Setup works

When an instance of Pretender is created, its constructor captures the global window.XMLHttpRequest object, replacing that global with its own implementation as an "interceptor". This implementation matches the XMLHttpRequest (via a library FakeXMLHttpRequest) and requesting libraries (e.g. 'jQuery') can interact with this implementation without knowing that the "real" XMLHttpRequest object is gone.

The main path into this implementation is the faked XMLHttpRequest.send method. This method returns immediately and the requesting library can listen for specific events.

Pretender, via configuration, allows a developer to control when these events trigger.

After setting up this interceptor, a Pretender instance runs its "maps". Maps are functions that, when executed set up pairings of HTTP methods (GET, POST, etc) and paths ('/some/url'). These are the methods/verbs that Pretender will respond to.

These methods all share the same implementation and are created by the private verbify function. They are: get, put, post, delete, patch, head, and options.

There is also a related functionality called "passthrough".

How Request Stubbing Works

Once Pretender has rudely snatched away the real XMLHttpRequest object and replaced it with a changeling child (FakeXMLHttpRequest) and a set of routes has been mapped, a Pretender instance is ready to start responding to requests.

When a requesting library (e.g "jQuery") makes an XMLHttpRequest, Pretender intercepts that request and responds with the [status, headers, body] data defined in the matching handler from the route map.

During setup, we stashed this defined handler in a registry for retrieval later.

When a request occurs, send is called out on our faked XMLHttpRequest. This eventually calls handleRequest

handleRequest looks up the handler and calls respond on this handler.

XMLHttpRequest.respond is not a thing. However, [FaxeXMLHttpRequest.respond](https://github.com/pretenderjs/FakeXMLHttpRequest/blob/fafac9799ac9989fa4798f2fe23b071c2209e04e/fake_xml_http_request.js#L462-L466) is! This triggers the response and the requesting library is none the wiser that no actual network requests were made!

How Request Proxying Works

Setup for passthrough is roughly the same but instead of going through a verbify method, you simply declare that a route is a passthrough this.get('/photos/:id', this.passthrough).

When a request for a passthrough route is made, well, that's where life gets interesting.

First, we check if the requesting library is asking for a HTTP method/url pair that we said to passthrough. You might think at this juncture "if that's the case, just give let the requesting library use the real XMLHttpRequest object and be done!", but, alas, there's no way to know whether to use FakeXMLHttpRequest or XMLHttpRequest until after a request is made and at point it's too late: the requesting library needs an instance of something.

And that something is a FakeXMLHttpRequest. But we also need to trigger real network interactions. This is done by creating a real XMLHttpRequest instance using our stashed copy of the constructor and, as the network interactions occur, triggering the matching method on the FakeXMLHttpRequest object that Pretender handed back to the requesting library when the request was made.

This means we have to handle all the various things a real network request can do. Uploads? Yep. Timeouts? Yes'm. HTTP Credentials. That too.

@rwjblue
Copy link
Contributor

rwjblue commented Oct 4, 2016

@trek - Thanks for writing up this detailed explanation / roadmap!

This will be of interest to @iezer and @chancancode, and could significantly help the work needed to consume ember-bench...

@trek
Copy link
Member Author

trek commented Jan 7, 2017

Related to emberobserver/client#51

@happycollision
Copy link
Contributor

The enhancement I authored that just shipped with 3.2.0 probably helps in this regard. Fake requests created by pretender now have a .passthrough() method on them that allows you to check the request and decide at runtime if you want to pass it through or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants