liveBook · Manning

Integration test

As explained in Functional Design and Architecture: Examples in Haskell

An integration test verifies that multiple software components work together correctly in an environment representing their actual integration conditions.

An integration test checks whether integrated application parts satisfy requirements together, especially at subsystem boundaries and when interacting with real services. It treats the application as a black box and runs in an environment intended to resemble production conditions. section 15.1 section 15.1.4

This realism provides confidence that components cooperate in practice, but it also makes tests slower, harder to set up, and less reliable than isolated tests. Real databases, services, and configuration can introduce failures unrelated to the behavior being checked. section 15.1 section 15.1.4

Use integration tests to verify behavior that only emerges when parts are connected:

  • Check functional requirements across a substantial path through the application. section 15.1.4
  • Exercise interactions with other services and the edges between subsystems. section 15.1
  • Build a fast feedback loop alongside a minimum viable product, before every feature is complete. section 3.1.1 section 3.4

Keep production resources out of the test path. Pass configuration from outside the application so a test can select a specially prepared database rather than a production database. This separates environments and avoids risks such as corrupted data or security breaches while preserving a real integration path. section 15.1.4

Testing a labyrinth with real interpreters

A labyrinth-generation test runs the generateLabyrinth scenario through a real runtime, then inspects the produced labyrinth: its bounds must be 4 by 4, its wormhole count must fall within the requested range, and its exit count must do the same. For database loading, inject a test RocksDB configuration instead of embedding a production path in the scenario. section 15.1.4 section 15.1.4

Integration testing is valuable but incomplete. It is difficult to reproduce precisely timed failures such as a database outage in a real environment; dependency injection and mocking support more controlled white-box tests for those cases. section 15.2.1

Listing 15.6 Test for labyrinth-generation functionality
spec :: Spec
spec = around (withCoreRuntime Nothing)
  describe "Labyrinth generation tests"
    $ it "generateLabyrinth" $ \runtime -> do
  
      let action = generateLabyrinth (4, 4) 3 5        #1
      lab <- runLangL runtime action                   #2
  
      let (bounds, wormholes, exits) = analyzeLabyrinth lab
  
      bounds `shouldBe` (4, 4)
      (Map.size wormholes)
         `shouldSatisfy` (\x -> x >= 2 && x <= 5)
      (Set.size exits)
         `shouldSatisfy` (\x -> x >= 1 && x <= 3)

Inspect the real-runtime labyrinth test to see an integration path followed by assertions on observable results. section 15.1.4

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Defines black-box integration testing, demonstrates real-interpreter execution, and details environment separation for database-backed scenarios.
Examples
Uses a spaceship-control simulator and Labyrinth generation and RocksDB-loading scenarios.
What it assumes
Assumes familiarity with application subsystems and services while introducing integration testing as black-box testing.
What you can do after
Apply it via real interpreters and externally supplied test configuration to verify integrated behavior safely.
How it connects
Connects minimum viable products, functional testing, property-based testing, dependency injection, and mocking.

Also explained in

Compare how 31 books explain integration test →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.