0

I'm trying to figure out how to create unit tests for a function, which behavior is based on a third party service.

Suppose a function like this:

def sync_check():
    delta_secs = 90
    now = datetime.datetime.now().utcnow()
    res = requests.get('<url>')
    alert = SlackAlert()
    last_value = res[-1]['date'] # Last element of the array is the most recent
    secs = (now - last_value).seconds

    if secs >= delta_secs:
        alert.notify("out of sync. Delay: {} seconds".format(secs))
    else:
        alert.notify('in sync')

What's best practice to write unit test for this function? I need to test both if and else branches, but this depends on the third party service.

The first thing that come to my mind is to create a fake webserver and point to that one (changing url) but this way the codebase would include testing logic, like:

if test:
    url = <mock_web_server_url>
else:
    url = <third_party_service_url>

Moreover, unit testing would trigger slack alerts, which doesn't have to happen.

So there I shoulde change again the codebase like:

if secs >= delta_secs:
    if test:
        logging.debug("out of sync alert sent - testing mode")
    else:
        alert.notify("out of sync. Delay: {} seconds".format(secs))
else:
    if test:
        logging.debug("in sync alert sent - testing mode")
    else:
        alert.notify('in sync')

Which I don't really like.

Am I missing any design to solve this problem?

Manuel Fedele
  • 1,390
  • 13
  • 25
  • 2
    If you're unit testing, you can mock out `SlackAlert` entirely, using e.g. [`unittest.mock`](https://docs.python.org/3/library/unittest.mock.html). You could also consider injecting the alert handler, rather than instantiating it inside that method, to reduce coupling. For a more integration-based test, look into something like [`responses`](https://github.com/getsentry/responses), depending on what client that library uses, for mocking out the network layer. You certainly **should not** have `if test:` logic inside your production code. – jonrsharpe Mar 20 '19 at 13:44

1 Answers1

0

Check out Dependency Injection to test code that depends on third party services, without having to check whether you're running in test mode, like in your example. The basic idea is to have the slack alert service be an argument of your function, so for unit testing you can use a fake service that acts the way you want it to for each test.

Your code would end up looking something like this:

def sync_check(alert):
    delta_secs = 90
    now = datetime.datetime.now().utcnow()
    res = requests.get('<url>')
    last_value = res[-1]['date'] # Last element of the array is the most recent
    secs = (now - last_value).seconds

    if secs >= delta_secs:
        alert.notify("out of sync. Delay: {} seconds".format(secs))
    else:
        alert.notify('in sync')

and in a test case, you could have your alert object be something as simple as:

class TestAlert:
    def __init__(self):
        self.message = None

    def notify(self, message):
        self.message = message

You could then test your function by passing on an instance of your TestAlert class, and check the logged output if you want to, by accessing the message attribute. This code would not access any third party services.

def test_sync_check():
    alert = TestAlert()
    sync_check(alert)
    assert alert.message == 'in sync'