How to expect some messages and ignore others with akka.net testkit? - testing

I have a test for an actor that might respond with some unexpected messages but eventually it must respond with a particular known message.
So in essence I want an assertion that will within in some timespan ignore other messages but expect a known message, like so:
[TestMethod]
[TestCategory("Integration")]
public async Task Should_fetch_fund_shareclass_and_details_from_test_service()
{
var testIsins = new HashSet<string> {"isin1", "isin2", "isin3"};
var props = Props.Create(() => new DataFetchSupervisor());
var actor = Sys.ActorOf(props, "fetchSupervisor");
Within(TimeSpan.FromSeconds(30), () =>
{
actor.Tell(new StartDataFetch(testIsins));
//ignore unexpected messages here
var fetchComplteMsg = ExpectMsg<DataFetchComplete>();
});
}
So now this will fail because I get some other messages before DataFetchComplete message.
As always, thanks in advance for any help.

Akka.TestKit class has a number of different versions of ExcpectMsg or equivalent checkers. The one you're looking for is probably a FishForMessage. It takes a predicate and will ignore all incoming messages as long as they will fail to pass predicate's condition. Once a passing message is found this check will complete and your code may be continued.

You can also use the TestKit.IgnoreMessages method, which will accept a delegate function specifying which types of messages you wish to ignore while waiting for your target message.

Related

Kotlin - Here Maps - Get address out of callback function

I am attempting to get the address out of the callback function. I have been reading the documentation for CallBacks and some posts but still don't get why this is not working, as at the moment of returning the 'address' variable the callback has already finished.
private fun getAddressForCoordinates(geoCoordinates: GeoCoordinates):String {
address = "unchanged"
val maxItems = 1
val reverseGeocodingOptions = SearchOptions(LanguageCode.EN_GB, maxItems)
searchEngine.search(geoCoordinates, reverseGeocodingOptions, addressSearchCallback)
return address
}
private val addressSearchCallback =
SearchCallback { searchError, list ->
if (searchError != null) {
//showDialog("Reverse geocoding", "Error: $searchError")
Toast.makeText(context, "Error: $searchError", Toast.LENGTH_LONG).show()
return#SearchCallback
}
Toast.makeText(
context,
"Reverse geocoded address:" + list!![0].address.addressText,
Toast.LENGTH_LONG
).show()
address = list[0].address.addressText
}
From your code and comment I assume you are not familiar with the concept of asynchronous execution. That concept was well described here. I'll quote the main point:
When you execute something synchronously, you wait for it to finish
before moving on to another task. When you execute something
asynchronously, you can move on to another task before it finishes.
The fact that search() requires providing a callback and it doesn't simply return search results, is a good indication that it is most probably asynchronous. Invoking it is like saying: "Search for the data in the background and let me know when you have it. This is my email address - please send me my results there". Where email address is your callback. Invoking search() method does not block execution of your code, it does not wait for results - it only schedules searching and returns almost immediately.
Asynchronous processing is usually more tricky than a regular, synchronous code, but in many cases it is more efficient. In your case you can either try to "convert" original async API of the library to sync API that your code expects - but this is not recommended approach. Or you can redesign your code, so it will work asynchronously. For example, instead of doing this:
fun yourMethodThatNeedsAddress() {
val address = getAddressForCoordinates()
doSomethingWithAddress(address)
}
You need to do this:
fun yourMethodThatNeedsAddress() {
scheduleGetAddressForCoordinates() // renamed getAddressForCoordinates()
}
fun addressSearchCallback() {
...
doSomethingWithAddress(address)
}
So, whatever you planned to do with the acquired address, you can't do this straight after you started searching. You need to wait for a callback and then continue with processing of your address from there.
The SearchEngine from the 4.x HERE SDK needs an online connection as it is fetching results from a remote backend. This may take a few milliseconds, depending on your network connection. So, whenever you perform a search request, you need to wait until the callback is called:
searchEngine.search(geoCoordinates, reverseGeocodingOptions, addressSearchCallback)
When you call this, you pass addressSearchCallback as parameter. The implementation for addressSearchCallback can look like in your example. It will be called whenever the operation has finished. If the device is offline, then an error will be shown.
Note that the search() method is not returning any results immediately. These are passed to the callback, which happens asynchronously on a background thread. Thus, your application can continue to work without blocking any UI.
Once results are retrieved, the callback will be executed by the HERE SDK on the main thread.
So, if your code needs to do something with the address result, you have to do it inside the onSearchCompleted() method defined by the SearchCallback. If you write it in plain Java without lambda notation, it is more visible: You create a new SearchCallback object and pass it as parameter to the SearchEngine. The SearchEngine stores the object and executes the object's onSearchCompleted() whenever it thinks it's the right time:
private SearchCallback addressSearchCallback = new SearchCallback() {
#Override
public void onSearchCompleted(#Nullable SearchError searchError, #Nullable List<Place> list) {
if (searchError != null) {
showDialog("Reverse geocoding", "Error: " + searchError.toString());
return;
}
// If error is null, list is guaranteed to be not empty.
showDialog("Reverse geocoded address:", list.get(0).getAddress().addressText);
// Here is the place to do something more useful with the Address object ...!
}
};
I took this from this GitHub code snippet. Note that there is also an OfflineSearchEngine, that works without an internet connection, but for some reason it follows the same pattern and executes the task asynchronously.
private void getAddressForCoordinates(GeoCoordinates geoCoordinates) {
int maxItems = 1;
SearchOptions reverseGeocodingOptions = new SearchOptions(LanguageCode.EN_GB, maxItems);
searchEngine.search(geoCoordinates, reverseGeocodingOptions, new SearchCallback() {
#Override
public void onSearchCompleted(#Nullable SearchError searchError, #Nullable List<Place> list) {
if (searchError != null) {
showDialog("Reverse geocoding", "Error: " + searchError.toString());
return;
}
// If error is null, list is guaranteed to be not empty.
showDialog("Reverse geocoded address:", list.get(0).getAddress().addressText);
}
});
}
SearchEngine, a SearchOptions instance needs to be provided to set the desired LanguageCode. It determines the language of the resulting address. Then we can make a call to the engine's search()-method to search online for the address of the passed coordinates. In case of errors, such as when the device is offline, SearchError holds the error cause.
The reverse geocoding response contains either an error or a result: SearchError and the result list can never be null at the same time - or non-null at the same time.
The Address object contained inside each Place instance is a data class that contains multiple String fields describing the address of the raw location, such as country, city, street name, and many more. Consult the API Reference for more details. If you are only interested in receiving a readable address representation, you can access addressText, as shown in the above example. This is a String containing the most relevant address details, including the place's title.
Please refer to following link for detailed documentation on search() function and parameters associated with it.
https://developer.here.com/documentation/android-sdk-explore/4.4.0.2/dev_guide/topics/search.html

Stopping a repeating scheduled message

We want an actor to stop itself after a period of some inactivity (i.e. after it hasnt received a certain kind of message for x minutes). I don't see any built-in support for this, so I've opted to use the scheduler.
The actor itself sets a scheduled message from itself to itself, as follows:
Context
.System
.Scheduler
.ScheduleTellRepeatedly(_expiryInterval, _expiryInterval, Self, new ExpiryCheckMessage(), Self);
On receiving this message:
Receive<ExpiryCheckMessage>(x => {
if(IsExpired())
{
Context.Stop(Self);
}
});
But after expiring and stopping the actor the scheduled messages keep sending resulting in deadletters.
What is the best way to stop a scheduled message in this case?
NB: I'm familiar with Akka on the JVM, not on .Net.
From the Akka docs for ScheduleTellRepeatedly it appears that there's an optional cancelable parameter of type ICancelable. So I would imagine that something like (this is literally the first C# I've ever attempted to write, so apologies in advance):
// Somewhere in the actor's scope
var cancellationKey = new Cancelable(Context.System.Scheduler);
Context
.System
.Scheduler
.ScheduleTellRepeatedly(
_expiryInterval,
_expiryInterval,
Self,
new ExpiryCheckMessage(),
Self,
cancellationKey
);
Receive<ExpiryCheckMessage>(x =>
if (IsExpired()) {
cancellationKey.Cancel();
Context.Stop(Self);
}
}

How to stop replyTo so that #SendTo works

I have Java code similar to this in a class called "MyService" to receive messages, process the object passed and return a response, with the intention to have the returned response using the configured exchange and routing key, as specified using the #SendTo annotation:
#RabbitListener(containerFactory = "myContainerFactory", queues = RabbitConfig.MY_QUEUE_NAME)
#SendTo("#{T(com.acme.config.RabbitOutboundConfig).OUTBOUND_EXCHANGE_NAME + '/' + myService.getRoutingKey()}")
public OrderResponse handlePaidOrder(Order order) {
// do processing on the input Order object here...
OrderResponse orderResponse = new OrderResponse();
// fill up response object here
return orderResponse;
}
public String getRoutingKey() {
String routingKey;
// .. custom logic to build a routing key
return routingKey;
}
This makes sense and works fine. The problem I am having is I can't figure out how to stop the "reply_to" property from coming in the message. I know if my sender configures a RabbitTemplate by calling setReplyAddress, that will result in a reply_to property and a correlation_id in the message.
However, if I simply do not call setReplyAddress, I still get a reply_to property, one that looks like this:
reply_to: amq.rabbitmq.reply-to.g2dkAAxyYWJiaXRAd3NK and so forth
and with that reply_to in the message, #SendTo has no effect. The Spring AMQP docs and this post: Dynamic SendTo annotation state:
The #SendTo is only used if there's no replyTo in the message.
Furthermore, when I don't call setReplyAddress on the RabbitTemplate, I don't get a correlation-id either. I pretty sure I am going to need that. So, my question is, how do I get my sender to generate a correlation-id but to not generate a reply-to so that my receiver can use the #SendTo annotation?
Thanks much in advance.
The correlationId is for the sender; it's not needed with direct reply-to since the channel is reserved; you could add a MessagePostProcessor on the sending side to add a correlationId.
The #SendTo is a fallback in case there is no reply_to header.
If you want to change that behavior you can add an afterReceivePostProcessor to the listener container to remove the replyTo property on the MessageProperties.
container.setAfterReceivePostProcessor(m -> {
m.getMessageProperties().setReplyTo(null);
return m;
}
Bear in mind, though, that if the sender set a replyTo, he is likely expecting a reply, so sending the reply someplace else is going to disappoint him and likely will cause some delay there until the reply times out.
If you mean you want to send an initial reply someplace else that does some more work and then finally replies to the originator, then you should save off the replyTo in another header, and reinstate it (or use an expression that references the saved-off header).

Is there an easy way to subscribe to the default error queue in EasyNetQ?

In my test application I can see messages that were processed with an exception being automatically inserted into the default EasyNetQ_Default_Error_Queue, which is great. I can then successfully dump or requeue these messages using the Hosepipe, which also works fine, but requires dropping down to the command line and calling against both Hosepipe and the RabbitMQ API to purge the queue of retried messages.
So I'm thinking the easiest approach for my application is to simply subscribe to the error queue, so I can re-process them using the same infrastructure. But in EastNetQ, the error queue seems to be special. We need to subscribe using a proper type and routing ID, so I'm not sure what these values should be for the error queue:
bus.Subscribe<WhatShouldThisBe>("and-this", ReprocessErrorMessage);
Can I use the simple API to subscribe to the error queue, or do I need to dig into the advanced API?
If the type of my original message was TestMessage, then I'd like to be able to do something like this:
bus.Subscribe<ErrorMessage<TestMessage>>("???", ReprocessErrorMessage);
where ErrorMessage is a class provided by EasyNetQ to wrap all errors. Is this possible?
You can't use the simple API to subscribe to the error queue because it doesn't follow EasyNetQ queue type naming conventions - maybe that's something that should be fixed ;)
But the Advanced API works fine. You won't get the original message back, but it's easy to get the JSON representation which you could de-serialize yourself quite easily (using Newtonsoft.JSON). Here's an example of what your subscription code should look like:
[Test]
[Explicit("Requires a RabbitMQ server on localhost")]
public void Should_be_able_to_subscribe_to_error_messages()
{
var errorQueueName = new Conventions().ErrorQueueNamingConvention();
var queue = Queue.DeclareDurable(errorQueueName);
var autoResetEvent = new AutoResetEvent(false);
bus.Advanced.Subscribe<SystemMessages.Error>(queue, (message, info) =>
{
var error = message.Body;
Console.Out.WriteLine("error.DateTime = {0}", error.DateTime);
Console.Out.WriteLine("error.Exception = {0}", error.Exception);
Console.Out.WriteLine("error.Message = {0}", error.Message);
Console.Out.WriteLine("error.RoutingKey = {0}", error.RoutingKey);
autoResetEvent.Set();
return Task.Factory.StartNew(() => { });
});
autoResetEvent.WaitOne(1000);
}
I had to fix a small bug in the error message writing code in EasyNetQ before this worked, so please get a version >= 0.9.2.73 before trying it out. You can see the code example here
Code that works:
(I took a guess)
The screwyness with the 'foo' is because if I just pass that function HandleErrorMessage2 into the Consume call, it can't figure out that it returns a void and not a Task, so can't figure out which overload to use. (VS 2012)
Assigning to a var makes it happy.
You will want to catch the return value of the call to be able to unsubscribe by disposing the object.
Also note that Someone used a System Object name (Queue) instead of making it a EasyNetQueue or something, so you have to add the using clarification for the compiler, or fully specify it.
using Queue = EasyNetQ.Topology.Queue;
private const string QueueName = "EasyNetQ_Default_Error_Queue";
public static void Should_be_able_to_subscribe_to_error_messages(IBus bus)
{
Action <IMessage<Error>, MessageReceivedInfo> foo = HandleErrorMessage2;
IQueue queue = new Queue(QueueName,false);
bus.Advanced.Consume<Error>(queue, foo);
}
private static void HandleErrorMessage2(IMessage<Error> msg, MessageReceivedInfo info)
{
}

Is it possible to continue with task C after A and B run to completion without fault or cancellation using a single TPL method?

I've tried to use Task.Factory.ContinueWhenAll() a few times now with the intent of invoking a continuation only when all the antecedents run to completion without any errors or cancellations. Doing so causes an ArgumentOutOfRangeException to be thrown with the message,
It is invalid to exclude specific continuation kinds for continuations off of multiple tasks. Parameter name: continuationOptions
For example, the code
var first = Task.Factory.StartNew<MyResult>(
DoSomething,
firstInfo,
tokenSource.Token);
var second = Task.Factory.StartNew<MyResult>(
DoSomethingElse,
mystate,
tokenSource.Token);
var third = Task.Factory.ContinueWhenAll(
new[] { first, second },
DoSomethingNowThatFirstAndSecondAreDone,
tokenSource.Token,
TaskContinuationOptions.OnlyOnRanToCompletion, // not allowed!
TaskScheduler.FromCurrentSynchronizationContext());
is not acceptable to the TPL. Is there a way to do something like this using some other TPL method?
There doesn't appear to be a direct way to do this. I've gotten around this by changing OnlyOnRanToCompletion to None and checking to see if Exception is non-null for each task passed into the continuation. Something like
private void DoSomethingNowThatFirstAndSecondAreDone(Task<MyResult>[] requestTasks)
{
if (requestTasks.Any(t => t.Exception != null))
return;
// otherwise proceed...
}
works, but this doesn't seem to be a very satisfying way to handle the case with multiple antecedents and breaks with the pattern the single-case Task.Factory.ContinueWith uses.