0

I write a software which trades with crypto currency via big exchanges. Using the clean architecture approach I have defined some core entities, some business cases and then created an outer ring of adapters which implement my Marketplace interface. The function which retrieves the current spot price from the Kraken exchange looks like this:

    def get_spot_price(
        self, coin: str, fiat: str, now: datetime.datetime
    ) -> core.Price:
        answer = self.handle.query_public(
            "Ticker", {"pair": f"{map_normal_to_kraken(coin)}{fiat}"}
        )
        raise_error(answer, interface.TickerError)
        close = float(list(answer["result"].values())[0]["c"][0])
        logger.debug(f"Retrieved {close} for {fiat}/{coin} from Krakenex.")
        price = core.Price(timestamp=now, last=close, coin=coin, fiat=fiat)
        return price

It uses the krakenex library and converts the regular currency symbols (BTC) into Kraken's convoluted things (XBT, XBTC). Then it uses the external library and converts the result back into my entity core.Price.

In order to use that code, one has to use an API key from Kraken. Whatever test I would write, I would have to send a request to Kraken. This is not a fundamental unit test, and it would not be isolated. Also one would need to know a secret in order to run it. Therefore this isn't something that I'd want to have in my CI pipeline.

I have tried to push as much code into the inner architectural circles such that only this code really depend on the external library. But no matter where I set the boundaries, there will always be untested code. I have written a little command line entry point which sends a few test requests using the user's credentials. It is nice because the user can verify that the credentials are set up.

Is that the only way that I can test this sort of adapter code? For the rest of the program I can substitute a mock Marketplace, but how would I test the actual adapter here in a sensible fashion?

1
  • 2
    In cases like this you can test against another server, e.g. one that simply records requests made to it and exposes that information for the test to verify, to make sure the correct (as you understand it) request is being made, but that: 1. requires you to be able to inject an alternative URL in the test environment; and 2. doesn't catch issues where your understanding of the correct request is wrong (e.g. the external API changes).
    – jonrsharpe
    Nov 26 '21 at 10:19
6

This code has two related but distinct reponsibilities:

  • it retrieves information from an external system
  • it evaluates the answer and massages the data into the form you want.

These two reponsibilities should be separated, both because of the SRP and because it makes testing much easier. Your business code should contain a collaborator that is only responsible for external communication and which can easily be mocked away for testing the data-processing code.

1
  • In retrospect it is so obvious that this is the way to go! I have started to implement this, and it is looking very good. The coverage of that module is already at 73 % and I can likely bring it up to almost 100 %. I've written a blog post about the transformation. Dec 3 '21 at 10:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.