Unit testing functions with side effects?
Asked Answered
M

3

7

Let's say you're writing a function to check if a page was reached by the appropriate URL. The page has a "canonical" stub - for example, while a page could be reached at stackoverflow.com/questions/123, we would prefer (for SEO reasons) to redirect it to https://mcmap.net/q/57409/-java-lib-or-app-to-convert-csv-to-xml-file-closed - and the actual redirect is safely contained in its own method (eg. redirectPage($url)), but how do you properly test the function which calls it?

For example, take the following function:

function checkStub($questionId, $baseUrl, $stub) {
  canonicalStub = model->getStub($questionId);
  if ($stub != $canonicalStub) {
    redirectPage($baseUrl . $canonicalStub);
  }
}

If you were to unit test the checkStub() function, wouldn't the redirect get in the way?

This is part of a larger problem where certain functions seem to get too big and leave the realm of unit testing and into the world of integration testing. My mind immediately thinks of routers and controllers as having these sorts of problems, as testing them necessarily leads to the generation of pages rather than being confined to just their own function.

Do I just fail at unit testing?

Maggiemaggio answered 28/8, 2010 at 3:20 Comment(5)
Slow down... and rethink at what are you really trying to test here... If you remove the Redirect, the checkStub method doesn't do much so I'm not sure of the real test here. Usually you test the side effects that such a function causes.Griffiths
@gishu: yeah, it's an incredibly flawed example spawned from an earlier problem I've since sorted out.. the problem was fixed, but the example (redirecting) got stuck in my head. Focus on the end of the question - routers, controllers - as the real meat of the question. How do I test functions whose focus is largely "creating things" but which still need to be tested because of their own internal order and logic. I'm going to pass out now and I'll likely edit the hell out of this question in the morning. Hopefully you can see my intent now (it might help to know I'm brand new to unit testing).Maggiemaggio
You pretty much just stated you're mixing logic with object creation, which makes unit testing difficult. Can you split these two concerns? If so, writing your unit tests will be pretty trivial.Tuckie
@strager: Ok, maybe this is where I'm getting confused. Am I not supposed to test functions that create objects at all, and only test the "outside edges" - i.e. the "logic" methods - of the classes themselves? Again, I've never done unit testing before, but I thought all functions were supposed to be tested?Maggiemaggio
Test that the object creators are giving you the objects you expect (having a certain type with certain fields initialized, etc.). Test that the "logic methods" are giving you the proper results in nearly the same way.Tuckie
K
3

You say...

This is part of a larger problem where certain functions seem to get too big and leave the realm of unit testing and into the world of integration testing

I think this is why unit testing is (1) hard and (2) leads to code that doesn't crumble under its own weight. You have to be meticulous about breaking all of your dependencies or you end up with unit tests == integration tests.

In your example, you would inject a redirector as a dependency. You use a mock, double or spy. Then you do the tests as @atk lays out. Sometimes it's not worth it. More often it forces you to write better code. And it's hard to do without an IOC container.

Kruller answered 28/8, 2010 at 4:0 Comment(0)
B
1

This is an old question, but I think this answer is relevant. @Rob states that you would inject a redirector as a dependency - and sure, this works. However, your problem is that you don't have a good separation of concerns.

You need to make your functions as atomic as possible, and then compose larger functionality using the granular functions you've created. You wrote this:

function checkStub($questionId, $baseUrl, $stub) {
  canonicalStub = model->getStub($questionId);
  if ($stub != $canonicalStub) {
    redirectPage($baseUrl . $canonicalStub);
  }
}

I'd write this:

  function checkStubEquality($stub1, $stub2) {
    return $stub1 == $stub2;
  }

  canonicalStub = model->getStub($questionId);
  if (!checkStubEquality(canonicalStub, $stub)) redirectPage($baseUrl . $canonicalStub);
Brockie answered 2/2, 2015 at 21:39 Comment(0)
H
0

It sounds like you just have another test case. You need to check that the stub is identified correctly as a stub with both positive and negative testing, and you need to check that the page to which you are redirected is correct.

Or do I totally misunderstand the question?

Helical answered 28/8, 2010 at 3:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.