Attract Group Logo
Attract Group Logo

Unit Tests vs Integration Tests: Differences, Examples, and CI Strategy

15 min read
Vladimir Terekhov
Abstract unit and integration testing strategy with frosted glass layers connected to a crimson quality core on a luminous multi-color gradient background.

Unit tests vs integration tests is a question of scope, feedback speed, and risk. Unit tests check isolated logic quickly, while integration tests confirm that multiple components work together across real boundaries such as databases, APIs, queues, authentication, or payment services. A strong test strategy uses both, with fast unit tests protecting everyday code changes and targeted integration tests protecting business flows.

Unit tests vs integration tests: the practical difference

Unit tests and integration tests answer different risk questions. A unit test asks whether a small piece of code behaves correctly in isolation. An integration test asks whether multiple components behave correctly together, including calls across modules, services, databases, queues, or external APIs. Use both, but give each a narrow job.

AreaUnit testsIntegration tests
ScopeOne function, class, method, module, or domain serviceTwo or more components working together
SpeedUsually fast enough for frequent local runs and pull request gatesSlower because they may start services, seed data, call APIs, or use containers
DependenciesAvoids real infrastructure; uses simple fakes or mocks where neededUses real or test versions of databases, services, queues, file storage, auth, or APIs
OwnerUsually the developer who owns the code pathShared by developers, QA engineers, DevOps, and service owners
Failure signalPoints to a small code areaPoints to a boundary, configuration, contract, or environment issue
CostLower to run and maintain when code is testableHigher because environments, data, and dependency stability matter
Best useBusiness rules, calculations, validation, mapping, edge cases, error handlingCheckout, signup, scheduling, invoicing, payment, auth, API contracts, persistence

The difference between unit and integration testing is not about importance. It is about where the defect can appear. If the risk is inside a calculation, write a unit test. If the risk is in the way several parts communicate, write an integration test.

Microsoft Learn describes integration tests as tests that exercise two or more software components working together. It also recommends preferring a unit test when the same behavior can be covered either as a unit test or an integration test. That is a practical cost rule: test at the lowest level that gives reliable confidence.

When unit tests give the highest return

Unit tests give the highest return where behavior can be checked without starting the application stack. They are best for deterministic code: inputs go in, outputs or exceptions come out, and the test can name the expected behavior clearly. This makes them useful for early feedback during everyday development.

Use unit tests for code that should behave the same way regardless of database state, network timing, background jobs, or third-party availability. Good candidates include:

  • Domain rules, such as whether an order can be cancelled after shipment.
  • Validation logic, such as password rules, form validation, and required fields.
  • Calculations, such as taxes, discounts, commissions, refunds, usage billing, or payroll.
  • Mapping logic, such as converting API payloads into internal commands or DTOs.
  • State transitions, such as invoice draft to issued, appointment pending to confirmed, or subscription trial to paid.
  • Error handling, such as retry decision logic, exception mapping, and user-facing error messages.
  • Permission checks that do not need a live identity provider.
  • Boundary conditions, such as empty carts, expired coupons, overlapping bookings, or maximum file sizes.

Microsoft Learn's unit testing best practices make a clear recommendation: unit tests should avoid infrastructure dependencies. If a test needs a database, file system, network, or message broker to run, it is no longer giving the same fast and isolated feedback.

A useful unit test has these traits:

  • It is easy to run locally.
  • It does not require shared test data.
  • It has one clear reason to fail.
  • It names the behavior being tested.
  • It avoids testing private implementation details.
  • It does not duplicate the code it is supposed to verify.
  • It runs the same way on a developer laptop and in CI.

For example, an ecommerce product may have a discount rule: enterprise customers get a negotiated discount, but discounts cannot apply to restricted items. A unit test can cover regular items, restricted items, missing customer status, expired agreements, and rounding behavior without connecting to the product catalog or payment gateway.

In a healthcare scheduling product, a unit test can verify that a patient cannot book two appointments with overlapping times. That test should not need the actual calendar database. The overlap rule can be tested as domain logic, while a separate integration test can verify that the rule still works when requests pass through the API and persistence layer.

Unit tests are also useful during refactoring. When code is split into smaller services, moved into a new module, or cleaned up before scaling, a well-written unit suite tells the team whether core behavior stayed intact. This is one reason unit tests matter for long-lived products, not only for new builds.

When integration tests are worth the extra cost

Integration tests are worth the extra cost when risk sits between components rather than inside one function or class. They confirm that wiring, configuration, permissions, serialization, persistence, and network behavior match the product flow. Microsoft Learn describes integration tests as tests that exercise two or more software components working together.

Write integration tests when a defect would likely appear only after components interact. Common examples include:

  • API endpoint to application service to database.
  • Backend service to message queue to worker process.
  • Authentication middleware to role-based permissions.
  • Payment service to order service to invoice generation.
  • File upload API to object storage and metadata records.
  • Webhook receiver to signature verification and event processing.
  • Internal service to third-party API sandbox.
  • Multi-tenant data access rules across repositories and services.

Integration tests are useful for flows where contracts matter. A function may serialize a date correctly in isolation, while the API may still reject the payload because the consumer expects another format. A repository method may pass a unit test using a mock, while a real database query may fail because of a missing index, migration, constraint, or transaction setting.

Practical integration test examples:

Checkout flow

A checkout integration test can create a cart, apply a coupon, create an order, reserve inventory, call a payment sandbox, and store the payment result. This test is slower than unit tests for discount rules, but it protects a revenue path that depends on several services working together.

Appointment scheduling

A scheduling integration test can submit an appointment request through the API, check authentication, validate provider availability, write the booking, and publish a notification event. Unit tests can cover overlap rules, but integration tests confirm the full path uses those rules correctly.

Invoice creation

An invoice integration test can create billable usage, run invoice generation, write invoice records, attach tax details, and expose the invoice through an API. This protects against data mapping and persistence errors that a unit test may miss.

User signup

A signup integration test can create an account through the public endpoint, verify email uniqueness, assign the default role, create a tenant workspace, and trigger a confirmation email event. The test does not need to send a real email, but it should prove the application emits the correct event or request to the email service.

Integration tests should still be selective. If the behavior can be verified reliably as a unit test, prefer the unit test and keep the integration suite focused on boundaries. Broad integration coverage can become slow and hard to diagnose when every business rule is tested through the full stack.

How to place both tests in a CI/CD pipeline

CI/CD should separate fast confidence from deeper verification. Pull requests need quick checks that help reviewers decide whether a change is safe to merge. Broader integration suites can run after merge, on staging, or before release, where slower feedback is acceptable and environment coverage matters more.

GitHub's continuous integration guidance describes the common pattern: run build and test checks on commits and pull requests, then give reviewers pass/fail feedback. The challenge is deciding which tests belong in that fast gate and which tests should run later.

A practical CI layout looks like this:

Pipeline stageWhat to runPurposeNotes
Local developmentFocused unit tests for changed codeCatch logic errors before commitKeep commands simple and documented
Pull requestBuild, linting, unit tests, small contract checksProtect main branch without slowing reviewsFailures should point to a clear owner
After mergeWider integration suiteCatch boundary and configuration issuesUse stable test environments and repeatable data
StagingSmoke tests, selected integration tests, API checksVerify deployable build in a near-production setupKeep scope tied to release risk
Release or scheduled runsLonger integration, migration, compatibility, and security checks where neededDetect slower or environment-specific problemsTrack failures and assign ownership quickly

This setup follows the test pyramid idea discussed often in engineering teams: many low-level tests, fewer integration tests, and a smaller number of end-to-end tests. Google Testing Blog's "Just Say No to More End-to-End Tests" makes the economic case against relying too heavily on broad end-to-end suites because they tend to be slower, less stable, and harder to debug.

A healthy CI/CD test strategy usually includes:

  • Fast PR gates. Unit tests, static checks, and a small set of API or contract checks should run before merge.
  • Targeted integration runs. Tests that need databases, queues, containers, or external sandboxes can run after merge or on selected branches.
  • Smoke checks after deployment. These should confirm that the deployed application starts, routes traffic, connects to dependencies, and performs core actions.
  • Flaky-test quarantine. Do not leave unstable tests in the main gate. Quarantine them, assign an owner, and either fix, narrow, or remove them.
  • Clear ownership. Each failing test category should have an owner: product team, platform team, QA, DevOps, or service owner.
  • Test data control. Integration tests need predictable data setup and cleanup, especially for multi-tenant systems and regulated products.
  • Environment parity where it matters. Staging should match production for the dependencies that affect the tested behavior.

For a small product team, one CI workflow may be enough. For a larger SaaS platform, split workflows by speed and risk. A payment integration suite should not block every copy change, but it should run before payment-related code reaches production.

Common mistakes that make test suites expensive

Test suites become expensive when teams lose the signal behind a failure. A good failure points to a small area of code and suggests who should investigate. A weak failure says that something somewhere is broken, consumes time across several roles, and makes developers distrust the pipeline.

Avoid these common patterns:

Too many UI tests

Browser-based end-to-end tests are useful for a few core journeys, but they are expensive as the main safety net. They can fail because of timing, selectors, animation, browser differences, test data, network behavior, or unrelated UI changes. Use them for user journeys that truly need the full browser path.

Over-mocking

Mocks are useful when they isolate the code under test. They become harmful when the test only proves that the code calls a fake object in a specific order. If too many tests break during harmless refactoring, the suite may be testing implementation rather than behavior.

Integration tests that hide the failure source

A broad test that touches the API, database, queue, worker, email service, and third-party sandbox may fail often, but still give poor diagnostic information. Narrow integration tests are easier to debug. Test one boundary or one business flow at a time.

No test data strategy

Integration tests need predictable data. Random shared records, manual staging setup, and tests that depend on execution order create failures that waste engineering time. Use seeded fixtures, isolated tenants, generated records, teardown steps, or disposable environments where practical.

Ignored flaky tests

A flaky test is a process problem, not background noise. If the team learns to rerun failed builds without investigation, CI loses credibility. Either fix the test, move it out of the merge gate, or remove it if it no longer protects a meaningful risk.

Coverage vanity metrics

Coverage can show what code was executed, but it does not prove that business behavior is protected. High coverage with weak assertions still leaves risk. Use coverage as a diagnostic tool, then review whether the tests cover revenue flows, compliance behavior, permissions, and failure handling.

Testing every rule through the full stack

If every validation rule is tested only through the API and database, the suite becomes slow and harder to maintain. Test most rules at the unit level, then add a smaller number of integration tests to prove that the API calls the rules correctly.

No separation between unit and integration tests

When all tests run in one command with no categories, CI becomes hard to tune. Separate test projects, naming conventions, tags, or pipeline jobs help teams run fast checks frequently and slower checks at the right time.

How to choose the right testing mix for your product

The right testing mix depends on delivery risk, product maturity, architecture, and team capacity. Avoid copying a generic pyramid ratio. Instead, decide which defects would damage revenue, safety, compliance, customer trust, or release speed, then choose the cheapest reliable test type for each risk.

Use this decision framework by product stage.

MVP or early product

At MVP stage, speed matters, but skipping tests completely creates expensive rework. Focus on unit tests for core business rules and a small set of integration tests for the main user path.

Good starting mix:

  • Unit tests for pricing, validation, permissions, and domain rules.
  • Integration tests for signup, checkout, booking, payment, or another central flow.
  • Smoke tests for deployment health.
  • Manual exploratory QA for areas that change often.

Do not build a large automation framework before the product flow stabilizes. Keep tests close to current business risk.

Scaling SaaS product

As the product gains customers, regression risk increases. Teams need faster release cycles without relying on manual QA for every change.

Useful priorities:

  • More unit coverage around domain services and shared libraries.
  • Integration tests for APIs, database migrations, background jobs, and webhooks.
  • Contract tests between services where teams release independently.
  • CI gates that separate fast checks from longer runs.
  • Flaky-test ownership and reporting.
  • Test data patterns for tenants, roles, plans, and permissions.

This is often where a dedicated QA strategy review pays off. The goal is to reduce release risk without turning CI into a bottleneck.

Regulated, healthcare, fintech, or data-sensitive products

For regulated products, tests need to support traceability, security expectations, and audit readiness. Unit and integration tests should cover permissions, data handling, validation, audit events, and failure behavior.

Priorities often include:

  • Unit tests for compliance-sensitive rules.
  • Integration tests for authentication, authorization, audit logs, data retention, payments, and external reporting.
  • Security checks in CI where they fit the delivery process.
  • Clear records of what each automated test protects.
  • Stable staging workflows for release verification.

The NIST Secure Software Development Framework is a useful reference when security practices need to be part of the software delivery process, but testing should stay practical. Automate checks that reduce real product risk and make ownership clear.

Legacy modernization

Legacy systems often have limited unit coverage and tightly coupled code. Do not try to unit test every old class before making progress. Start by protecting current behavior at safer boundaries, then add unit tests as code becomes easier to isolate.

Practical steps:

  • Add integration tests around high-risk workflows before refactoring.
  • Capture current behavior for APIs, reports, billing, and data exports.
  • Refactor small areas behind tests.
  • Add unit tests for newly isolated domain logic.
  • Use CI to detect migration and compatibility issues early.

For legacy products, testing strategy often connects directly with architecture, DevOps, and modernization planning. Attract Group supports this through custom software development, DevOps and Cloud, and IT consulting services.

A good test mix is a business decision as much as an engineering decision. The team should know which risks the tests protect, how quickly feedback arrives, who owns failures, and which tests are allowed to block a release.

Build a Testing Strategy That Fits Your Release RiskReview unit, integration, regression, and CI gaps before slow or brittle tests start blocking delivery.

FAQs about unit tests vs integration tests

These short answers cover decisions that often slow teams down during test planning. Treat them as rules of thumb, then adjust for your architecture, release cadence, and operational risk. If a test does not give a clear failure signal, change its scope before adding more cases.

Are integration tests better than unit tests?

No. They answer different questions. Integration tests give confidence that components work together, while unit tests give fast feedback on isolated behavior. A healthy suite usually has many focused unit tests and fewer integration tests around important boundaries.

Should unit tests mock the database?

Usually, yes, if the test is meant to be a unit test. Database behavior belongs in integration tests. For unit tests, isolate the domain logic from persistence so the test does not depend on database state, migrations, or network behavior.

When should integration tests run in CI?

Run a small number of boundary checks in pull requests if they are fast and stable. Run broader integration suites after merge, on staging, or before release. The slower the test, the more selective its placement should be.

How many unit and integration tests do we need?

There is no universal ratio. Start from product risk. Cover business rules with unit tests, protect service boundaries with integration tests, and keep only a small number of broad end-to-end tests for core user journeys.

Share:
#Integration Test#Unit Tests
Vladimir Terekhov

Vladimir Terekhov

Co-founder and CEO at Attract Group

Frequently Asked Questions

Ready to Start Your Project?

Let's discuss how we can help you achieve your business goals with cutting-edge technology solutions. Get a free consultation to explore how we can bring your vision to life.

Or call us directly:+1 888-438-4988

Request a Free Consultation

Your data will never be shared with anyone.