Web Development

The Mock That Lies to Your Face: Why I'm Moving Away From WireMock

A

Admin User

Author

Jun 10, 2026
4 min read
3 views

I learned something humbling last month at 2 AM during a production incident. My order service was calling a payment gateway that had silently changed its API response field names. My entire test suite was green. My mocks were working perfectly. The real service? Completely broken for us. That's the moment I realized I'd been building confidence on sand.

For years, I've been using WireMock to stub out external services. It's convenient, it's fast, and it lets me test in isolation without depending on live third-party APIs. What I didn't realize until recently is that convenience comes with a hidden cost: WireMock lets me define whatever contract I want, and the real service doesn't know anything about my assumptions. I was writing fiction and calling it a test.

This is exactly the problem Pact solves—and it's a paradigm shift I wish I'd understood sooner.

The Confidence Trap Nobody Talks About

Here's what haunts me about that 2 AM incident: everything on my end was working correctly. My tests passed. My mocks responded exactly as I'd programmed them. The payment gateway team made a completely reasonable change on their end—standardizing field names across their API. Also reasonable. Neither team was wrong.

But we didn't talk to each other through our code.

With WireMock, you're essentially maintaining a private fiction about what another service does. You control that fiction entirely. The real service evolves independently. Until those two realities collide in production, you have no idea they've diverged.

The trap is insidious because your tests remain green the entire time. You feel safe. You deploy with confidence. And then someone pages you at midnight.

How Pact Inverts the Problem

Pact works backwards from WireMock's model. Instead of you writing a stub and hoping it matches reality, your tests declare what they need from a provider service. Those declarations get serialized into a contract file—a machine-readable .pact file that the actual provider service can verify against.

The key insight: the provider runs verification. If a provider ships code that breaks your declared contract, verification fails before deployment. The provider team has to know what you depend on because the contract tells them.

It's a completely different trust relationship. You're not guessing anymore. You're making explicit promises and verifying them upstream.

The Implementation Reality

I've been experimenting with pact-python to replace WireMock on a few projects, and I need to be honest: the experience is rougher than the documentation suggests.

The biggest gotcha I hit: pact-python v3 is a rewrite backed by Rust FFI, and it handles fixtures completely differently than v2. The standard pytest pattern of a module-scoped fixture that multiple tests append to will break silently with a cryptic error message. The Rust handle gets consumed on the first serve() call, and you can't add new interactions after that point.

Here's what actually works:

# ✅ Define all interactions upfront, before serve()
def test_payment_gateway_contract():
    pact = Consumer("OrderService").has_pact_with(
        Provider("PaymentGateway"),
        port=8888
    )
    
    # Define all interactions first
    (pact
     .given("payment succeeds")
     .upon_receiving("a charge request")
     .with_request("POST", "/payments/charge")
     .will_respond_with(200, body={
         "status": "ACCEPTED",
         "transaction_id": "txn-123",
         "amount": 134.97
     }))
    
    (pact
     .given("payment declined")
     .upon_receiving("a decline")
     .with_request("POST", "/payments/charge")
     .will_respond_with(400, body={
         "status": "DECLINED",
         "reason": "insufficient_funds"
     }))
    
    # Then serve and run all tests against the contract
    with pact:
        response = requests.post(
            "http://localhost:8888/payments/charge",
            json={"amount": 134.97}
        )
        assert response.json()["transaction_id"]

Define everything first. Serve once. Test against that fixture. The documentation could be clearer about this constraint, but once you understand it, the pattern is clean.

My Take

I'm convinced Pact catches something real that WireMock can't: upstream changes you'd otherwise miss until production. That alone is worth the friction of adoption.

But I'm also realistic: Pact adds operational complexity. You need buy-in from provider teams. You need to publish and verify contracts in your CI/CD pipeline. For internal microservices where you control both sides, it's straightforward. For third-party APIs where you have no control over provider verification? The value drops significantly.

My current approach: use Pact for critical downstream dependencies you control. Use WireMock for external APIs where you can't force upstream verification. Make the distinction explicit.

The real lesson isn't "WireMock is bad." It's that mocking without verification is a false sense of security. Pick your tool based on whether you can actually verify the contract.

What Would You Do?

Are you dealing with microservice integration tests right now? How are you validating that your mocks match reality? I'm curious whether Pact feels like a no-brainer adoption for you or another framework tax.

Source: This post was inspired by "How Pact Contract Testing Catches Breaking Changes That WireMock Misses" by Dev.to. Read the original article

Share this article

Related Articles