Martin Fowler's Object Mother Pattern. An Easy Win.

Share
Martin Fowler's Object Mother Pattern. An Easy Win.

Let's talk about a guardrails in software. We all have certain objects, or pieces of state, that we don’t want anyone touching directly. But how do you enforce that? You can’t rely on a wiki page that says “Don’t update this field”, or a code walkthrough where someone points at lines and warns “Don’t touch these.” Those rules are too easy to ignore.

Instead, we use code itself to create the guardrails. That’s what Encapsulation is about. By marking fields private and exposing setters/getters, we force developers to go through deliberate methods instead of mutating state directly. You can’t just do person.name = "new name". You have to call setName("new name"), which makes the intent explicit and keeps the object’s state under control.

public class Person {
    private String name;

    public void setName(String newName) {
        name = newName;
    }

    public String getName() {
        return name;
    }
}

Encapsulation stops reckless mutation in production code. But tests have the same problem. Mock data often gets declared once and reused everywhere — and a single mutation can ripple across the entire suite.

The Ripple Effect

If you read my article on Declaring Mock Data in Different Files, you know that having seperate files for complex and heavily reused objects is the ultimate life hack for making spec files much smaller and readable (while having the option to make updates to the object on an as needed bassis). But there's one gotcha that often goes overlooked when teams do this:
When you declare mock data like this in a seperate "non-spec" file

export const mockUser = {
  id: 1,
  name: "Alice",
  active: true
};

And you change it once in the should deactivate a user test case, the "post-mutated" state of mockUser carries over into the subsequent tests.

user.spec.ts

import { mockUser } from "./user-mock";

describe("User tests", () => {
  it("should deactivate a user", () => {
    mockUser.active = false;  // 🚨 Mutating the shared object
    expect(mockUser.active).toBe(false);
  });

  it("should still be active by default", () => {
    // Surprise! The object is already mutated from the previous test
    expect(mockUser.active).toBe(true);  // ❌ This will fail
  });
});

When you export a constant object from a shared file, every spec that imports it receives the same reference. That means if one test mutates it, the updated state doesn’t just leak into later tests in the same file — it can also leak into completely different spec files. Since ES modules are cached singletons, once a value is changed, all other imports see that change (see MDN docs)

import { mockUser } from "./user-mock";

describe("Profile tests", () => {
  it("should still be active by default", () => {
    // Surprise! This spec runs *after* user.spec.ts in the same process,
    // so mockUser is already mutated
    expect(mockUser.active).toBe(true);  // ❌ This will fail
  });
});

Solution: Martin Fowler's Object Mother Pattern

The first move is to create fixture in the setup method of an xunit test - that way it can be reused in multiple tests. But the trouble with this is often you need similar data in multiple test classes. At this point it makes sense to have a factory object that can return standard fixtures.
-Martin Fowler

How this ties into our last article:

In Declaring Mock Data in Different Files, I showed how having the large reusable/complex object in one file, and updating peices of it on an as needed basis keeps specs clean and maintainable. This is largely what Martin Fowler talks about, but with one wrinkle on top of it, the factory pattern. The wrinkle is subtle but important: instead of exporting an object literal (shared and mutable), we export a function that returns a new object each time.

export const createMockUser = (): User => ({
  id: 1,
  name: "Alice",
  active: true
} as User);

This doesn’t make the object itself immutable, but it does make it isolated. Each call produces a fresh instance, so no test can poison another (and ensures no side effects in other spec files using this factory).

import { createMockUser } from "./user-factory";

describe("User tests", () => {
  it("should deactivate a user", () => {
    const user = createMockUser();   // ✅ fresh instance
    user.active = false;
    expect(user.active).toBe(false);
  });

  it("should still be active by default", () => {
    const user = createMockUser();   // ✅ unaffected by other tests
    expect(user.active).toBe(true);
  });
});
💡
Look at the end of the factory method:

export const createMockUser = (): User => ({ id: 1, name: "Alice", active: true } as User);

By using type casing (as User), we avoid any typing issues when passing mock data into any methods expecting this type of data, and if we were to update any of the properties, we get the added benefits of IntelliSense or whatever snipping technology our IDE is using.

Spread Operator

Even though the spread operator doesn’t give you more safety than createMockUser() already provides, it’s still a great habit. It makes your intent crystal clear: “start from the baseline, then override what matters.”

describe("User tests", () => {
  it("should deactivate a user", () => {
    const user = { ...createMockUser(), active: false }; // ✅ override via spread
    expect(user.active).toBe(false);
  });

  it("should still be active by default", () => {
    const user = createMockUser(); // ✅ unaffected by other tests
    expect(user.active).toBe(true);
  });
});
So even if someone does mutate their local `user`, the factory ensures no other tests get polluted. The spread operator just makes the override pattern explicit, while the underlying safety net comes from always creating a fresh instance.

Wrapping Up

In my last post, I argued for pulling mocks into their own files and updating them as needed. Object Mother takes that one step further: keep your reusable objects, but wrap them in a factory so every test starts clean. It’s a small shift that gives you safety without losing flexibility.

It’s like when your spouse keeps misplacing your comb. You could keep reminding them not to touch it, or you could just tuck it in a drawer where they won’t mess with it. No harm, no foul.

Guardrails > gotchas. Get more easy wins in your inbox

Subscribe