No clients bother to do this, in the real world - they just hard code/compose the URLs that they need to use. Why make extra http calls when you don't have to? Why parse all the json-hal (or whatever) to "figure out" which URL to call next, when you don't have to?
Even if most clients did this, you can't enforce it, so not all of them will, so some will still break when you change URLs.
1) It makes manual testing annoying, 2) I have a nagging feeling that if my users are "doing it wrong" then maybe the API is doing it wrong...
Also I've been playing with autogenerating client implementations using autogenerated swagger specs, and that approach is incompatible with an actual opaque linked API. It would be nice to have the best of both worlds.
There's a good question about how long you can cache those URLs for as well; it's a non-starter for a client to have to traverse the whole tree from the root for every request. So can I cache the responses for the duration of my auth token, and get a new root node as part of my re-auth?
If you go down that route, now you need to maintain two versions again during migration (but you do keep the ability for 'well-behaved' clients to migrate versions without downtime).
As the sibling comment describes, you _can_ enforce this by obfuscating your URLs, but I've not had the guts to do that yet...
Another approach would be to write great client libraries yourself, so that you know that the clients are consuming the API correctly.
If you have an object which links to `/users/1/`, and want to change that URL to `/cool_users/1/`, what's the migration path?
Without HATEOAS, you need to update all your clients' code to now generate the new base URL `/cool_users/`. This means you'll need to version your API, so that old clients can continue to access the old-style endpoints in the transition period. (Note that for a business where your customers are making an API integration, this means you're imposing work on your customers).
With HATEOAS, you just need to update the URLs that are returned in your other endpoints. (Generally there is one well-known entry-point into your API, e.g. you return {"user_list": "/users/", ...} with your login token, for example). Now, assuming your clients were using `api.user_list`, that they received, they will without further modification fetch the `/cool_users/` endpoint, without requiring an update.
The one gotcha is if clients are holding on to the IDs of your API objects between API calls; in that case, you will break any code which expects to find those previously-returned members. But note, the worst-case here is that you need to version your APIs, which was the best-case without HATEOAS. In many cases you can get away with such a change without any client-facing changes.