Phpunit Testing

By the end of this lesson you'll install PHPUnit with Composer and write real automated tests — assertions, data providers, setup/teardown, and mocks — so you can refactor and ship PHP with confidence instead of crossing your fingers.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Installing PHPUnit with Composer

PHPUnit is the standard testing framework for PHP — a tool that runs your tests and tells you which passed and which failed. You install it with Composer (PHP's package manager) as a dev dependency , meaning it's available while you build but never shipped to your live server. After installing, Composer places a runnable program at vendor/bin/phpunit .

One tiny config file tells the runner where your tests live, so you can just type ./vendor/bin/phpunit with no arguments. By convention, source code goes in src/ and tests go in tests/ .

2️⃣ Your First TestCase

A test is just a class that extends TestCase , with one or more public methods whose names start with test . Inside each method you follow Arrange-Act-Assert : build what you need, call the one thing you're testing, then check the result with an assertion . An assertion is a line that says "this had better be true" — if it isn't, the test fails. To check that code throws , you call expectException() before the line that should throw.

Read the output bottom-up: OK (2 tests, 2 assertions) means both methods ran and every assertion held. Each dot in .. is one passing test; a failure shows as F , an error as E .

3️⃣ The Assertions You'll Use Most

PHPUnit ships dozens of assertions, but a handful cover nearly everything. The key distinction is assertEquals versus assertSame : assertEquals compares values loosely (so 5 equals "5" ), while assertSame compares value and type strictly (like === ). Reach for assertSame by default — it catches accidental string-vs-int bugs. The exception is floats, where rounding means you use assertEqualsWithDelta .

4️⃣ setUp() and tearDown(): Fresh State Every Test

Tests must be isolated — each one runs as if it were the only test in the world. PHPUnit helps by calling setUp() before every test method , so you can build a fresh object there instead of repeating yourself. The matching tearDown() runs after every test to undo side effects (delete temp files, roll back a database). Because setUp() re-runs each time, no test can leak state into the next.

Now you try. The test class below is almost complete — fill in each ___ with the right assertion name using the 👉 hint, then run it and check it against the Output panel.

5️⃣ Data Providers: One Test, Many Inputs

When you want to test the same logic against lots of inputs — valid and invalid emails, edge-case numbers — don't copy-paste the test. A data provider is a static method that returns an array of rows ; each row is the arguments for one run. You attach it with the #[DataProvider('methodName')] attribute, and PHPUnit runs the test once per row , reporting each as its own pass or fail. Adding a new case becomes a one-line change.

Your turn again. Below, a plain test needs to become data-driven. Fill in the expected numbers and the missing attribute name.

6️⃣ Mocking: Faking the Database and the Mailer

Real dependencies — a database, an email server, a payment API — make tests slow, flaky, and dangerous (you don't want a test emailing real users). The fix is a test double : a stand-in object. A stub just returns canned values so your code has something to work with. A mock goes further and verifies interactions — that a method was called the right number of times with the right arguments. PHPUnit's createMock() builds one from a class or interface; expects($this->once()) asserts it's called exactly once.

Notice the second test uses expects($this->never()) to prove a negative : a duplicate signup must not send a welcome email. That's behaviour you simply can't check by inspecting a return value — only a mock can assert it.

7️⃣ Running the Suite and Reading a Failure

You run everything with ./vendor/bin/phpunit tests/ . While debugging, narrow it down: ./vendor/bin/phpunit tests/CalculatorTest.php runs one file, and --filter testAddsTwoNumbers runs one method. When a test fails, PHPUnit prints the class, the method, what it expected vs what it got , and the exact file and line — read it carefully before touching code.

📋 Quick Reference — PHPUnit

No test code is filled in this time — just the class under test, a brief, and an outline. Write the test class yourself, run it with ./vendor/bin/phpunit , then check your result against the expected output in the comments. This is exactly the write-run-check loop you'll use on every real project.

Practice quiz

How do you install PHPUnit so it never ships to production?

  • composer require phpunit/phpunit
  • pecl install phpunit
  • composer require --dev phpunit/phpunit
  • apt install phpunit

Answer: composer require --dev phpunit/phpunit. The --dev flag adds PHPUnit as a dev-only dependency, available while you build but kept out of your live server.

What must a PHPUnit test class do, and how are test methods named?

  • Extend TestCase; public methods whose names start with 'test'
  • Implement Testable; methods end in Test
  • Extend Assert; methods named check*

Answer: Extend TestCase; public methods whose names start with 'test'. A test class extends PHPUnit's TestCase and has public methods whose names start with 'test'.

What is the difference between assertEquals and assertSame?

  • assertEquals is strict; assertSame is loose
  • They are identical
  • assertSame only works on arrays
  • assertEquals compares values with coercion; assertSame compares value AND type strictly

Answer: assertEquals compares values with coercion; assertSame compares value AND type strictly. assertEquals(5, "5") passes (loose), but assertSame(5, "5") fails because assertSame checks value and type like ===.

Which assertion should you use for floating-point comparisons?

  • assertSame
  • assertEqualsWithDelta
  • assertTrue
  • assertCount

Answer: assertEqualsWithDelta. Rounding makes assertSame(0.3, 0.1 + 0.2) fail, so use assertEqualsWithDelta with a small delta for floats.

How do you assert that code throws an exception?

  • Call expectException(X::class) before the line that should throw
  • Wrap it in try/catch and call fail()
  • Use assertThrows()
  • Return the exception from the test

Answer: Call expectException(X::class) before the line that should throw. Call $this->expectException(InvalidArgumentException::class) before the call; the test fails if it never throws.

When does setUp() run?

  • Once before the whole test class
  • After every test method
  • Before every test method, giving each a fresh state
  • Only when a test fails

Answer: Before every test method, giving each a fresh state. setUp() runs before every test method, so building a fresh object there means no test can leak state into the next.

What is a data provider used for?

  • Connecting to a database in tests
  • Running the same test once per row of input/expected arguments
  • Mocking a mailer
  • Cleaning up after a test

Answer: Running the same test once per row of input/expected arguments. A data provider is a static method returning rows of arguments; #[DataProvider('name')] runs the test once per row.

What is the difference between a stub and a mock?

  • A stub verifies calls; a mock returns canned values
  • They are the same; only the name differs
  • A mock can only replace databases
  • A stub returns canned values; a mock also verifies interactions (how it was called)

Answer: A stub returns canned values; a mock also verifies interactions (how it was called). A stub just returns canned values; a mock additionally asserts interactions, e.g. expects($this->once())->method('send').

Which call builds a configurable test double you can add expectations to?

  • $this->createStub()
  • $this->createMock()
  • new RealClass()
  • $this->fake()

Answer: $this->createMock(). createMock() builds a double you can configure with willReturn and verify with expects(); createStub() makes a pure stub.

How do you run a single test file with PHPUnit?

  • ./vendor/bin/phpunit --only tests/CalculatorTest.php
  • phpunit run CalculatorTest
  • ./vendor/bin/phpunit tests/CalculatorTest.php
  • ./vendor/bin/phpunit --file=CalculatorTest

Answer: ./vendor/bin/phpunit tests/CalculatorTest.php. Pass the file path to run one file; use --filter testName to run a single method.