Unit-testing – Are integration tests meant to repeat all unit tests

integration-testsrubytestingunit testing

Let's say I have a function (written in Ruby, but should be understandable by everyone):

def am_I_old_enough?(name = 'filip')
   person = Person::API.new(name)
   if person.male?
      return person.age > 21
   else
      return person.age > 18
   end
end

In unit testing I would create four tests to cover all scenarios. Each will use mocked Person::API object with stubbed methods male? and age.

Now it comes to writing integration tests. I assume that Person::API should not be mocked any more. So I would create exactly the same four test cases, but without mocking Person::API object. Is that correct?

If yes, then what's the point of writing unit tests at all, if I could just write integration tests which give me more confidence (as I work on real objects, not stubs or mocks)?

Best Answer

No, integration tests should not just duplicate the coverage of unit tests. They may duplicate some coverage, but that's not the point.

The point of a unit test is to ensure that a specific small bit of functionality works exactly and completely as intended. A unit test for am_i_old_enough would test data with different ages, certainly the ones near the threshold, possibly all occurring human ages. After you've written this test, the integrity of am_i_old_enough should never be in question again.

The point of an integration test is to verify that the entire system, or a combination of a substantial number of components does the right thing when used together. The customer doesn't care about a particular utility function you wrote, they care that their web app is properly secured against access by minors, because otherwise the regulators will have their asses.

Checking the user's age is one small part of that functionality, but the integration test doesn't check whether your utility function uses the correct threshold value. It tests whether the caller makes the right decision based on that threshold, whether the utility function is called at all, whether other conditions for access are satisfied, etc.

The reason we need both types of tests is basically that there is a combinatorial explosion of possible scenarios for the path through a code base that execution may take. If the utility function has about 100 possible inputs, and there are hundreds of utility functions, then checking that the right thing happens in all cases would require many, many millions of test cases. By simply checking all cases in very small scopes and then checking common, relevant or probable combinations f these scopes, while assuming that these small scopes are already correct, as demonstrated by unit testing, we can get a pretty confident assessment that the system is doing what it should, without drowning in alternative scenarios to test.