-
Notifications
You must be signed in to change notification settings - Fork 94
Allow clients to use the already used port on a standalone server #21
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
Allow clients to use the already used port on a standalone server #21
Conversation
Hmm, I'm not sure about this one. You're doing this so that you can get two Mockttp client instances for the same port, right? The problem is that Mockttp client instances have state. They hold a websocket open to subscribe to handlers like I think there's probably an alternate solution. Is it not possible to call Is it possible for you to share the test code you're working on, so I can take a closer look? Alternatively of course, you could use Mockttp as a proxy, which might help. Have you investigated that? In that can you can intercept the whole application, it can send requests anywhere it likes, and you can intercept them for all sorts of different URLs however you'd like. If there's a concrete case where none of those are possible I'm open to looking at this kind of change, but I'd like to avoid it if we can. |
I will try some of your ideas. I could not share some real code because I am not allowed to do this. What I am using is webdriver.io (selenium based test framework) as testing framework against a mocked implementation of an . I try to set up a mock server in the test set up. The problem with this framework in particular is that I could not share the instance of mockttp via the global object. The place where I am able with this framework, it tries to instance more than one mockttp server at once. To avoid code duplications I extract the mocking methods in a single file that need a running server. The current setup look something like this (with my changes): Webdriver IO setup
'mocker'
example test
This is the basic setup. I hope that helps by understanding the reasons for this pull request. |
Also experiencing a similar issue using Jest. |
@chrisandrews7 in general, you shouldn't really need to share an instance at all: the preferred approach is to not share a server between the tests, and start & configure a separate one within each test. Effectively to stick with Jest's philosophy, and isolate the tests from one another completely. Mockttp supports that out of the box, is there a reason that doesn't work in your case? If so, have you tried any of the suggestions discussed above? If you do need to share one mock server between multiple tests, and you need to reconfigure the server during those tests, then you're going to need a way to coordinate between them somehow. There's options there but they're definitely awkward, and I'd recommend avoiding it and keeping your tests independent instead, if you can. |
@pimterry What would you suggest in the case where the application under test is being run in a docker container, and changing the url of the service I'm trying to mock is not really possible? As the test runner isnt starting/controlling the application? |
So the application you're testing is run once for the whole test suite, and shared by all the tests? Yeah, that makes this tricky. Given that setup I assume all your tests are running sequentially, not in parallel? As long as that's true you can do something like:
That should work for most cases, and keeps things fairly simple. Is there anything that would stop that working for you? |
Thanks @pimterry thats what I'm currently doing. But ideally would like to run in parallel? |
Ok, great, good start! Doing that in parallel is hard: you've got multiple tests, unpredictably interleaving requests to the mocked server, and interleaving reconfigurations of the mocked server. Even if Mockttp & Jest both supported what you need to do this directly, it'll be messy and it'll make your tests very brittle if you're not extremely careful. Do you definitely need to configure the server within the tests? Could you simplify and give each endpoint a preconfigured fixed response? Sharing a server is easy, if you don't need to reconfigure it during the tests. Alternatively, is there a way you can know which requests are for which test? Maybe you know that each test only talks to a specific service or endpoint? That would make this possible: you can set up a single central mock server on a single fixed port, let each test run its own mock server on its own fixed port which it can freely reconfigure, and then preconfigure rules on the central server to forward requests to the relevant per-test mock servers (because you know by looking at the request which test is relevant). It's possible, but still difficult to do nicely. If you don't have a way to clearly distinguish all requests, and you do need to reconfigure at runtime, this is hard and potentially impossible. For example if you have two tests which both result in requests to the same mocked endpoint, and you want to have the two tests get different responses, there's never going to be a way you can reliably run those tests in parallel - you'll never know which configuration each test will end up using. |
Thanks @pimterry for the thorough reply. You're quite right, I think this is one of those square peg round hole situations! |
Hello @pimterry, sorry to bring up this issue. I'm trying to do exactly what you described: have one proxy to all my parallel tests. In your last message, I'm interested in the third paragraph where you said "you can set up a single central mock server on a single fixed port, let each test run its own mock server on its own fixed port which it can freely reconfigure, and then preconfigure rules on the central server to forward requests to the relevant per-test mock servers". I can easily know which request comes from what test (with the ID from the parameter/query/body endpoint). But I don't understand how I can forward requests from the central mock server to the "client" mock server. I would love to know how to do this. |
Hi @amoinier!
In that case, I think you can do this with something roughly like: centralServer.forAnyRequest().thenPassThrough({
beforeRequest: (req) => {
const targetUrl = calculateTargetUrl(req);
return {
url: targetUrl
};
}
}); This examines each received request, and forwards it to a different upstream server according to the details of the request (e.g. the test id you mentioned, or any other logic you want to put into With that, you can send every test request from all tests to this single central mock server, and then the proxy can redirect them to the appropriate independent mock test servers for each test. Those per-test mock servers can be defined separately with any rules you like, safe in the knowledge that each is isolated from the other tests. Does that make sense? |
Ok, that makes sense! I can approach my goal with that, thanks. But it's not completely perfect (it's my fault; I haven't been quite precise). In my case, when I say "I can easily know which request comes from what test (with the ID from the parameter/query/body endpoint)", I want to say that my requests are all unique, so I will never have conflicts between my parallel tests. So with your proposal, I can't forward to a specific independent mock test server. But, It won't cause problem for me if I can forward to multiple mock test servers. Do you think it's possible to do that? |
Hmm, I'm afraid I don't understand - I'm not sure what "forward to multiple mock test servers" means, and if every test's requests are different, I don't understand why can't you use the above code to do what you're looking for. Can you explain the full context and some more details about the issues you're facing? |
TL;DR I'm trying to set rules of requests from client mock servers, but I'm receiving requests on my global server. I have a Node.js server app using Axios with a proxy config on port 5555 to make requests to xxx.com/user/:id. I start my app and then my E2E tests (Mocha) start making requests. In my Mocha global config, I start a proxy server to mock the xxx.com/user/:id call, and I want to mock it directly in my tests. However, with Mocha, I can't start a global mockttp server (port 5555) and access it from my parallel tests, so I need to create a client mock test server for each parallel process of tests (2 processes in my case). So I have a global mock server and two client mock servers (port 8000 & 8001). Now, I want to mock requests from the client mock server because I can access it from my tests. But the real requests are being forwarded to my global server, and not to the client mock server (because my Node.js app is already started and the proxy config is already set to 5555). So I tried (I know it's a bit tricky and I shouldn't use mockttp like that, but development can be hard sometimes) ^^ |
Ah, I see, you're saying that you're trying to forward a single request to the central server to both test mock servers at the same time? That's definitely not going to be possible with the standard APIs I'm afraid. If it were, it's not clear exactly how it would work - if they both return completely different but valid responses, which one should be used? In practice, when you define two rules like this that match the same request, by default each rule will run once (for the first received request) until the last matching rule, which will keep running forever. That's the default though, you can also tweak this using methods like In this case though, I don't think you need to do any of that. From what you describe, I think this is totally possible with a single central rule, especially if it's possible to tell which server should be used from the user id in the URL. If so, you can do this with central server logic like this: centralServer.forAnyRequest().thenPassThrough({
beforeRequest: (req) => {
const parsedUrl = new URL(req.url);
const userIdMatch = /\/user\/(.*)/.exec(parsedUrl.pathname);
if (!userIdMatch) return; // Ignore non-matching requests
// Redirect to the server responsible for the relevant user id:
const userId = userIdMatch[1];
if (userId === "123") return { url: 'http://localhost:8000' };
else if (userId === "456") return { url: 'http://localhost:8001' };
else throw new Error("Unrecognized user id");
}
}); As long as you can recognize the ids used for each test, that will do what you want. If they're not, this is still possible, but you'll need to expose this from the test servers to share it with the central server, e.g something like this: // At the start of the test:
testMockServer1.forGet('/mock-data/userIds').thenJson(["123", "abc"]);
testMockServer2.forGet('/mock-data/userIds').thenJson(["456", "def"]);
// In the central server:
centralMockServer.forAnyRequest().thenPassThrough({
beforeRequest: async (req) => {
const parsedUrl = new URL(req.url);
const userIdMatch = /\/user\/(.*)/.exec(parsedUrl.pathname);
if (!userIdMatch) return; // Ignore non-matching requests
// Query the mock servers to work out which ids to look for:
// (you could probably cache this if it becomes problematic)
const [server1Ids, server2Ids] = await Promise.all([
fetch('http://localhost:8000/mock-data/userIds').then(r => r.json()),
fetch('http://localhost:8001/mock-data/userIds').then(r => r.json())
]);
const userId = userIdMatch[1];
if (server1Ids.includes(userId)) return { url: 'http://localhost:8000' };
if (server2Ids.includes(userId)) return { url: 'http://localhost:8001' };
else throw new Error("Unrecognized user id");
}
}); There's lots of other ways to do this, there are many flexible mechanisms you could use to communicate between the tests and central proxy, but I think that's probably the simplest clearest one that would do that job. Does that make sense? |
Wow, it works (almost) like a charm! Thanks a lot for this proposal. I'm trying to implement it correctly with my codebase; it's not perfect but the job is done. (Sorry for my late response; my scope has changed for the last two weeks and I wanted to test your solution before responding ^^) |
For test setups where you are not able to change the request url of the application that uses the mock server.
With this changes you are able to instance a client for a standalone server when the port is already used by another client.