Testing the GenStages Under Umbrella
In the previous article, we discussed the concept of GenStage as a way of communication between Umbrella apps. Now is time to write some tests for the future GenStage under Umbrella implementation.
We won’t build an interface for the project. We will write just two end to end tests to assert the information flow in the Umbrella.
We will add two new applications to the Umbrella:
- a
Master
app which will have the four main ones as dependencies. This will allow us to write end to end tests without mocking. - a
Shared
app that will hold code to be reused in the other apps. The four main apps will haveShared
as a dependency.
This step is not required for the functionality of the project. It is helpful for the tests and DRY-ing up our code.
This is what our umbrella will look like:
Let’s create the apps.
▶ mix new stockr --umbrella
▶ cd stockr/apps/
▶ mix new master --sup
▶ mix new ger_market --sup
▶ mix new usa_market --sup
▶ mix new converter --sup
▶ mix new my_uk_app --sup
▶ mix new shared --sup
We add all applications in the Umbrella as Master
dependency:
And in the mix.exs file of the four main apps add Shared
as dependency: {:shared, in_umbrella: true}
.
The Tests
Writing the tests will give us a better idea about what we want to achieve. Also, we do not want the tests to test the GenStages themselves but only the given input and the expected output. Our integration tests will be written in the Master
app. As Master
has our main apps as dependencies, we can be sure that they are started when the tests run.
Receiving Info
We start with the case when we receive stock market information.
At the top of the test we callShared.Interface.start_link(MyUkAppInterface, self())
The Shared
app has an Interface
module. We use this will simulate an action we do with the data received by our apps. Eg: display to the users, make a HTTP post, etc. In our case, it just starts a GenServer for the MyUkApp
with the Test process ID as argument. We use the Shared
app because the Interface
code is similar for all apps. The Interface
will send the messages received in the GenStage consumers to the Test process and we’ll be able to assert_receive
them. We implement the Shared.Interface
module below, after we finish writing the tests.
We manually subscribe the GenStage consumers to the producers. We will come back to this topic in the last article of this series to see how to automate this step.
A standard market info message will contain the company, the price per share and the currency. A message once received, will be sent to the ReceiveProducer
if there is any demand. We will not implement a receiving mechanism for this demo. We push data directly to ReceiveProducer.receive_info/1
.
The expected_info
is what we want to receive in MyUkApp
. Prices are converted to GBP. We will use fixed exchange rates in our application:
1 USD = 0.75 GBP
1 EUR = 0.90 GBP
Finally, we assert that we receive the correct messages.
Sending Info
Now let’s see what happens when we send back stock market info.
The test is very similar to the one above. We start the Interface for the GerMarket
and UsaMarket
.
The real difference is how we subscribe the consumer to the producer. This time we use the :selector
option. The producer will broadcast the information to all consumers. The consumer will receive only the data filtered by the selector.
We send the markets info with MyUkApp.SendProducer.send_info/1
. In the end, we expect to receive the information in EUR and USD.
The Interface
The Shared.Interface,
in our case, is just a test helper. It will pass the messages from the GenStage consumers to the test process.
The GenServer starts with the name
and test_pid
as arguments. It holds the test_pid
as state. We will use the process_info(name, test_pid)
in the actual implementation of the GenStage consumers. The handle_call/3
callback sends the info to the test process and we can assert_receive
it.
At this point, we are ready for the next post in the series: the actual implementation of the GenStages.