Testing the GenStages Under Umbrella

GenStage Under Umbrella — Part 2

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 have Shared 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
▶ 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:

./apps/master/mix.exs

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.

./apps/master/test/receive_info_test.exs

At the top of the test we call
Shared.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.

.apps/master/test/send_info_test.exs

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.

./apps/shared/lib/shared/interface.ex

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.