Verdict
No
No, do not test a third-party library's own features; test your code that uses it, with the real library running and inputs that give a different result if the behaviour you rely on changes, and run those tests on every dependency upgrade.
Why
- Blast radiususers
- Change frequencyrarely
- Detectabilityimmediately
- Reversibilitytrivial
- Test costmoderate
No, leave a library's own features to its maintainers and test the code where you use the library. The typical case is a maintained package, such as a date or UI library, pinned in a lockfile and run without mocks by your own tests. Blast radius is users, and Change frequency is rarely, because the library changes in your app only when you raise its version. Detectability is immediately and Reversibility is trivial: an upgrade that changes a result you rely on fails your tests in its pull request, and you leave it unmerged. Test cost is moderate, because a library test needs the library's setup. With these values rule R8 gives Do not test.
| When | Decision | Why |
|---|---|---|
| Your tests mock a library that parses input you save, such as dates in a CSV import | Test minimally: one import test with the real library that asserts the saved value | Detectability moves to eventually and Reversibility to with-effort: an upgrade that changes a parsing default saves plausible wrong values |
| A decimal library rounds the invoice totals your code computes | Test mandatory: invoice totals with the real library at each rounding boundary | Blast radius rises to money and Reversibility to costly: a wrong total ends in refunds |
| You configure an authentication library, such as Devise, to decide who may open a page | Test mandatory: one allowed and one denied request for each rule you configure | Blast radius rises to safety-or-legal and Detectability to never: a wrong rule lets users in without an error |
| Your team forked the library and patches it | Test: tests for each patch, run in your own CI | Change frequency rises to regularly, Detectability moves to eventually and Reversibility to with-effort: no upstream suite runs your patches |
| Your own tests build slugs from titles with capital letters, and a test of the slug library would take two minutes | Do not write the library test | Test cost falls to trivial, but Detectability stays immediately and Reversibility trivial: your tests fail on an upgrade that changes the slugs |
What breaks if you don't test
Skipping tests of the library itself breaks nothing that those tests would catch. The failures I see come from how an application uses a library: a wrong format pattern, an option left at its default, a mock that returns a value the real library never returns. The library's own suite passes in each case. When your tests mock the library, an upgrade that changes a default also passes CI, and a user reports a wrong saved value days later.
What you lose if you over-test
Library tests repeat work the maintainers already run in their own CI, and they fail on upgrades for details your application never reads, such as the wording of an error message. Each failure blocks an upgrade until someone edits a test that caught no bug, and a team that pays this often starts to delay upgrades, security fixes included.
What to do instead
- Test your code that calls the library, with the real library running. Mock it only where it reaches a network, a paid service or the clock.
- Pick inputs whose result changes if the behaviour you rely on changes, such as a date on 31 December for a year pattern.
- Pin versions in a lockfile and install with
npm cior its equivalent, so CI runs what you ship. - Let Dependabot open one pull request per upgrade, so your tests run against each new version before it merges.
When the answer changes
- Your tests mock the library, so no test runs its real code.
- The library decides who may sign in, or computes amounts you charge.
- Your team forks the library and patches it.
Real incident + Code example
The ticket for New Year's Eve 2026
On a ticketing product I worked on, PDF tickets printed the event date with DateTimeFormatter.ofPattern("dd MMM YYYY"). Our test used a date in March and passed. In Java's pattern table, Y is the week-based year, and for 31 December 2025 that year is already 2026. New Year's Eve tickets went on sale in October and printed "31 Dec 2026". Three weeks later a buyer asked support about the year, and we reissued about 1,900 PDFs. A test of the JDK formatter would have passed, because it printed what our pattern asked for. This test of our own code fails on the bug:
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.LocalDate;
import org.junit.jupiter.api.Test;
class TicketDatesTest {
// On 31 December 2025 the week-based year (YYYY) is already 2026
@Test
void printsTheCalendarYearOnNewYearsEve() {
assertEquals("31 Dec 2025", TicketDates.print(LocalDate.of(2025, 12, 31)));
}
}
// The fix in TicketDates: DateTimeFormatter.ofPattern("dd MMM yyyy", Locale.UK)
Related questions
- Should I test an API client?Yes
- Should I test ORM code?Yes
- Should unit tests make real API calls?Test it differently
- Should integration tests use mocks?Code under test: Yes
- Should I test trivial code?No
FAQ
- Should my unit tests also test third-party library features?
No, unit tests should check your code and leave a library's features to the library's own suite. Run the real library inside your unit tests where it needs no network, so an upgrade that changes a result you rely on fails them.
- Should you test the gems your Rails app uses?
No, a Rails app needs no tests of a gem's own features, such as Devise hashing a password. Test how your app configures the gem: request specs for protected pages, signed in and signed out.
- Should I mock third-party libraries in unit tests?
Mock a third-party library in unit tests only when it calls a network, a paid service or the clock. A mocked library returns whatever the test tells it to, so an upgrade that changes the real results passes every test that mocks it.
- Do I need to test Material UI components?
No, do not test that a Material UI button renders or calls its click handler. Test your component that uses the button: what the user sees and what happens after a click, with Testing Library queries by role.