How to set default response for MockWebServer? - unit-testing

MockWebServer is awesome library, but there is one thing that is surprisingly hard to do: set default response.
To be specific: I want to have ability to set response that is returned if no response was specified using server.enqueue(response).
I would like to be able to do something like:
server.setDefaultResponse(okResponse)
server.enqueue(customResponse)
And then when my tests call server twice (or more), every response after the first one would be okResponse.
When tests get more complicated and multiple calls to server are needed, sometimes specifying every single response (often simple 200 OK) is tedious and pollutes tests.
Is there any simpler way to do this than creating your own Dispatcher? Creating it properly (with support for multiple responses) sounds like overkill for such a small thing.

There is improvement that can be done in comparison to implementing your own Dispatcher. When looking at MockWebServer implementation I found that its default dispatcher is QueueDispatcher.
And it has some very handy methods, like:
public void setFailFast(boolean failFast)
public void setFailFast(MockResponse failFastResponse)
setFailFast(true) sets server to "fail fast" mode i.e. if no response in enqueued, it doesn't wait, just returns HTTP 404 response immediately.
setFailFast(okResponse) sets response to return in "fail fast" mode, which exactly answers this question.
The problem is, you don't have direct access to dispatcher field in MockWebServer, so what you need to do is set your own instance of QueueDispatcher and then set default response (or "fail fast" response) on it, like that:
val dispatcher = QueueDispatcher()
dispatcher.setFailFast(okResponse)
server.setDispatcher(dispatcher)

Related

Apollo iOS how to handle partial decoding failure

I'm trying to see if there is a way to do more robust handling of partial decoding failures of Apollo generated Swift classes. Currently, if even one field of one object in an array fails to parse from the network response, the entire collection of objects fails to parse and our iOS client gets no data.
Our graphql is defined something like:
query mobile_getCollections() {
getCollections() {
// ... other fields
items {
activeRange {
expires // Int!
starts // Int!
}
}
}
}
So the Apollo generated Swift code is expecting non-nil Ints when decoding these values. However, due to a backend error (that we would like to make the mobile clients more resilient to), the API will occasionally send us a malformed date String instead of a unix timestamp Int. This causes parsing of the entire mobile_getCollections result to fail, because the Apollo generated query class typing can't be perfectly satisfied.
Ideally, I'd like to just throw out the one item in the collection that failed to be parsed correctly and leave the remaining items intact. Is it possible to do something like that using Apollo?
(Yes, I know the real answer here is to fix the backend, but is there anything I can do in the mean time to more gracefully handle similar partial parsing failure issues?)

How to make order-independent assertions on Flux output?

I have a test case for a Flux from Project Reactor roughly like this:
testMultipleChunks(StepVerifier.FirstStep<Chunk> verifier, Chunk chunk1, Chunk chunk2) {
verifier.then(() -> {
worker.add(chunk1);
worker.add(chunk2);
worker.update();
})
.expectNext(chunk1, chunk2)
.verifyTimeout(Duration.ofSeconds(5));
}
Thing is, my worker is encouraged to parallelize the work, which means the order of the output is undefined. chunk2, chunk1 would be equally valid.
How can I make assertions on the output in an order-independent way?
Properties I care about:
every element in the expected set is present
there are no unexpected elements
there are no extra (duplicate) events
I tried this:
testMultipleChunks(StepVerifier.FirstStep<Chunk> verifier, Chunk chunk1, Chunk chunk2) {
Set<Chunk> expectedOutput = Set.of(chunk1, chunk2);
verifier.then(() -> {
worker.add(chunk1);
worker.add(chunk2);
worker.update();
})
.recordWith(HashSet::new)
.expectNextCount(expectedOutput.size())
.expectRecordedMatches(expectedOutput::equals)
.verifyTimeout(Duration.ofSeconds(5));
}
While I think that makes the assertions I want, it took a terrible dive in readability. A clear one-line, one-method assertion was replaced with four lines with a lot of extra punctuation.
expectedRecordedMatches is also horribly uninformative when it fails, saying only “expected collection predicate match” without giving any information about what the expectation is or how close the result was.
What's a clearer way to write this test?
StepVerifier is not a good fit for that because it verifies each signal as it gets emitted, and materialize an expected order for asynchronous signals, by design.
It is especially tricky because (it seems) your publisher under test doesn't clearly complete.
If it was completing after N elements (N being the expected amount here), I'd change the publisher passed to StepVerifier.create from flux to flux.collectList(). That way, you get a List view of the onNext and you can assert the list as you see fit (eg. using AssertJ, which I recommend).
One alternative in recent versions of Reactor is the TestSubscriber, which can be used to drive request and cancel() without any particular opinion on blocking or on when to perform the assertions. Instead, it internally stores the events it sees (onNext go into a List, onComplete and onError are stored as a terminal Signal...) and you can access these for arbitrary assertions.

How to return a boolean for whether or not a QUrl redirects?

I have been playing around with this, and I can not quite follow the logic for redirections in qt. Really, all I would like to do is build a function like so:
bool MyClass::isRedirect(const QUrl &url)
{
return QUrl(m_NetworkAccessManager.get(QNetworkRequest(url))
->header(QNetworkRequest::LocationHeader).toString()).toString()
!= url.toString();
}
And for proof of concept, I'd like it done with this url:
https://en.wikipedia.org/wiki/Vario-Tessar
It should redirect to:
https://en.wikipedia.org/wiki/Tessar#Vario-Tessar
I figure this should be really easy, but the answer is evading me. I tried putting in the url, and checking the reply for a header indicating a redirect, but none came up. Reading the documentation... it took me for a loop as I could not quite grasp if I am supposed to set an attribute on the QNetworkReply, QNetworkRequest, or the QNetworkAccessManager itself for allowing redirects, and then afterwards, constructing a boolean.
I am also not sure if every redirect is the same, or if some pages implement it differently, having a script run after page load, like this:
https://en.wikipedia.org/w/index.php?title=Vario%20Tessar&action=edit
If I really wanted to, I could use a QWebEngineView to load, and just compare before and after urls, but that seems a bit hefty. I just want the simplest way to perform that function above.
Thanks.
I strongly suggest to look at the http status code of the reply, this way:
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
In your example it happens to be 200 (OK), i.e. no redirection at all.
Anyway, if you invoke
http://en.wikipedia.org/wiki/Vario-Tessar
(notice the missing s in the protocol name) a redirection will occur.
Given that the manager and/or the request are using the default redirect policy (QNetworkRequest::ManualRedirectPolicy), the response will sport a status code of 301, this time, and no data. Moreover you can check the redirection target:
int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
static QVector<int> redirectCodes{301, 302, 303, 305, 307, 308};
if(redirectCodes.contains(code))
{
QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
//...
At this point, you could start a new request using the redirect target, or abort the transaction altogether, or whatever your own personal redirect policy suggests you.
If you just don't want to deal with redirection at all, you can set a different policy at manager level, using QNetworkAccessManager::setRedirectPolicy, or at request level, using QNetworkRequest::setAttribute.

How to modify the HTTP::Response after it has been written to

I'm trying to write some tooling for Crystal (specifically Kemal) where I can see if the response content type is text/html and modify the response body thats has already been written to the HTTP::Response before it is sent to the client by injecting an HTML element into the existing html response body.
I've noticed that HTTP::Server::Response is write-only, but things like Gzip::Writer are able to modify the body.
How can I modify the HTTP::Server::Response html body before it is sent to the client?
It's written in Crystal, so let's just take a look at the source on how others do this.
Taking the CompressHandler as an example, the basic idea is to replace the response's IO with something that allows the desired control:
context.response.output = Gzip::Writer.new(context.response.output, sync_close: true)
# ...
call_next(context)
So how can we make use of that to modify the response that's being written?
A naive (and slow) example would be to just keep hold of the original output and provide a IO::Memory instead:
client = context.response.output
io = IO::Memory.new
context.response.output = io
call_next(context)
body = io.to_s
new_body = inject_html(body)
client.print new_body
Of course that would only work when this handler comes before any handler that turns the response into non-plaintext (like the above CompressHandler).
A smarter solution would provide a custom IO implementation that just wraps the original IO, watching what's written to it and inject whatever it wants to inject at the right point. Examples of such wrapping IOs can be found at IO::Delimited, IO::Sized and IO::MultieWriter among others, the pattern is really common to prevent unnecessary allocations.

Dynamic messages with gettext (AngularJS)

I have a application with a Django backend and an AngularJS front-end.
I use the angular-gettext plugin along with Grunt to handle translations.
The thing is, I sometimes received dynamic strings from my backend through the API. For instance a MySQL error about a foreign key constraint or duplicate key entry.
How can I add this strings to the .pot file or non harcoded string in general ?
I've tried to following but of course it cannot work :
angular.module('app').factory('HttpInterceptor', ['$q', '$injector', '$rootScope', '$cookieStore', 'gettext', function ($q, $injector, $rootScope, $cookieStore, gettext) {
responseError: function (rejection) {
gettext('static string'); //it works
gettext(rejection.data.error); //does not work
$rootScope.$emit('errorModal', rejection.data);
}
// Return the promise rejection.
return $q.reject(rejection);
}
};
}]);
})();
One solution I could think of would be to write every dynamic strings into a JSON object. Send this json to server and from there, write a static file containing these strings so gettext can extract them.
What do you suggest ?
I also use angular-gettext and have strings returned from the server that need to be translated. We did not like the idea of having a separate translation system for those messages so we send them over in the default language like normal.
To allow this to work we did two things. We created a function in our backend which we can call to retrieve all the possible strings to translate. In our case it's mainly static data that only changes once in a while. Ideally this would be automated but it's fine for now.
That list is formatted properly through code into html with the translate tag. This file is not deployed, it is just there to allow the extraction task to find the strings.
Secondly we created a filter to do the translation on the interpolated value, so instead of translating {{foo}} it will translate the word bar if that's was the value of foo. We called this postTranslate and it's a simple:
angular
.module('app')
.filter('postTranslate', ['gettextCatalog', function (gettextCatalog) {
return function (s) {
return gettextCatalog.getString(s);
};
}]);
As for things that are not in the database we have another file for those where we manually put them in. So your error messages may go here.
If errors are all you are worried about though, you may rather consider not showing all the error messages directly and instead determine what user friendly error message to show. That user friendly error message is in the front end and therefore circumvents all of this other headache :)