Save Your Scroll Finger: Why Mocks Belong in Their Own Files
Ever onboarded to a well-established Angular app, updated a module, and cracked open its spec file… only to find it starts with a hundred lines of mock data? Suddenly your day kicks off with five hard flicks of the scroll wheel just to get down to the beforeEach. It feels like nobody told the kids in daycare to clean up their toys.
import { TestBed } from '@angular/core/testing';
import { OrdersService } from './orders.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Order } from '../models/order.model';
const mockOrder: Order = {
id: 'o-001',
userId: 'u-123',
items: [
{ productId: 'p-111', quantity: 2, price: 29.99 },
{ productId: 'p-222', quantity: 1, price: 59.99 },
],
total: 119.97,
createdAt: new Date('2025-01-01T12:00:00Z'),
};
const mockOrder2: Order = {
id: 'o-002',
userId: 'u-456',
items: [{ productId: 'p-333', quantity: 5, price: 9.99 }],
total: 49.95,
createdAt: new Date('2025-01-02T14:00:00Z'),
};
const mockOrdersResponse: Order[] = [mockOrder, mockOrder2];
describe('OrdersService', () => {
let service: OrdersService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [OrdersService],
});
service = TestBed.inject(OrdersService);
});
it('should return a list of orders', () => {
service.getOrders().subscribe((orders) => {
expect(orders.length).toBe(2);
expect(orders[0].id).toBe('o-001');
});
});
});
Why does this happen? Why not just move the mock data into separate files? Because the inevitable questions pop up: “Where should the file go? Should I put it in the same folder, or somewhere more global in case it gets reused? Will it even be reused?”
Usually, the decision gets punted. You’re in feature-building mode and don’t want to pause for what feels like an “architecture problem.” But here’s the thing: avoiding the decision isn’t just laziness, it often reflects a lack of confidence in the project’s architecture rules. If your folder structure and conventions aren’t clear, developers default to shoving everything into the spec file.
Structures
So let’s tackle the simple question: how should we structure our application, and where do those models go?
There’s no true “one size fits all,” but the Angular Docs do provide some guardrails that make navigating the file structure simpler. For a more thorough explanation, check out the Angular Docs:
- Locate – put files where developers expect to find them
- Identify - name files clearly so it’s obvious what each one represents
represents. - Flat - avoid deep nesting whenever possible
generated .js and .js.map files. - T-DRY - Try to be DRY (but don’t over-engineer)
This is a sample from Medium,with models folders added by ChatGPT. I’d consider this representative of a medium-sized Angular application.
This is a shortened version of the structure — trimmed down to the most relevant parts so you can see where models fit in without listing every single file.
/src
└── app
├── core
│ ├── interceptors
│ │ └── auth.interceptor.ts
│ ├── guards
│ │ └── auth.guard.ts
│ ├── auth.service.ts
│ └── user.service.ts
│
├── shared
│ ├── components
│ │ ├── navbar/
│ │ └── sidebar/
│ ├── directives
│ │ └── debounce.directive.ts
│ ├── pipes
│ │ └── currency-format.pipe.ts
│ ├── models
│ │ └── (shared models go here)
│ └── shared.module.ts
│
├── features
│ ├── admin
│ │ ├── components
│ │ │ └── admin-dashboard.component.ts
│ │ ├── services
│ │ │ └── admin.service.ts
│ │ ├── models
│ │ │ └── (admin models go here)
│ │ ├── admin.module.ts
│ │ └── admin-routing.module.ts
│ │
│ ├── user
│ │ ├── components
│ │ │ ├── user-profile.component.ts
│ │ │ └── user-settings.component.ts
│ │ ├── services
│ │ │ └── user.service.ts
│ │ ├── models
│ │ │ └── (user models go here)
│ │ ├── user.module.ts
│ │ └── user-routing.module.ts
│ │
│ ├── products
│ │ ├── components
│ │ │ ├── product-list.component.ts
│ │ │ └── product-details.component.ts
│ │ ├── services
│ │ │ └── product.service.ts
│ │ ├── models
│ │ └── product.ts
│ │ └── buyer.ts
│ │ ├── products.module.ts
│ │ └── products-routing.module.ts
│ │
│ └── state
│ ├── reducers
│ │ ├── auth.reducer.ts
│ │ └── user.reducer.ts
│ └── actions
│ ├── auth.actions.ts
│ └── user.actions.ts
│
├── app.component.ts
├── app.module.ts
└── app-routing.module.ts
│
├── assets
├── environments
├── styles
├── main.ts
└── index.html
Where to Put Our Mock Folders
The .model files — where we keep our interfaces and classes — are the backbone. Every piece of mock data we create is shaped by one of these models.
So the real question becomes: where should those mocks live?
When placing mock data, think about two factors:
- Variability – How many different instances of this mock will you need across spec files?
- Size – If there’s lots of variability and the model is large, put it in a dedicated mock file. If it’s small, it’s often simpler to just inline it directly in the spec file.
Example: Feature-Specific Models
In a feature folder, every models directory (where your types/interfaces live) should have an adjacent mocks directory.
├── products
│ ├── components
│ │ ├── product-list.component.ts
│ │ └── product-details.component.ts
│ ├── services
│ │ └── product.service.ts
│ ├── models
│ │ ├── product.model.ts
│ │ └── buyer.model.ts
│ ├── mocks
│ │ ├── product.mock.ts
│ │ └── buyer.mock.ts
│ ├── products.module.ts
│ └── products-routing.module.ts
Why this works: in a small feature like products, you don’t usually need a wide variety of mocks. Keeping them side-by-side with the models keeps everything discoverable and easy to maintain.
Note: if you encounter a situation where mock data must look different for a different spec file, the respective feature folder, in this case products, leverage what you already created, you can always use spread operator.
it('should handle a discounted product correctly', () => {
const discountedProduct: Product = {
...mockProduct,
price: 19.99,
discount: 30
};
component.product = discountedProduct;
fixture.detectChanges();
expect(component.product.price).toBe(19.99);
expect(component.product.discount).toBe(30);
});
Example: Shared Models
The shared folder is typically where we keep generic utilities that can be reused across the entire application. Notice how files like date-range.model.ts, api-response.model.ts, and pagination.model.ts are broad in scope — they apply to many different situations, not just a single feature.
Because of this wide usage, their mocks may also be needed in multiple places. We can handle that in two ways:
├── shared
│ ├── components
│ │ ├── navbar/
│ │ └── sidebar/
│ ├── directives
│ │ └── debounce.directive.ts
│ ├── pipes
│ │ └── currency-format.pipe.ts
│ ├── models
│ │ └── api-response.model.ts
│ │ └── pagination.model.ts
│ │ └── date-range.model.ts
│ │ ├── user-profile.model.ts
│ │ ├── address.model.ts
│ │ └── audit-log-entry.model.ts
│ └── shared.module.ts
│
For smaller types like these:
pagination.model.ts
export interface Pagination {
page: number;
pageSize: number;
total: number;
}
date-range.ts
export interface DateRange {
start: Date;
end: Date;
}
We're going to initialize our mock data on a case by case basis in the spec files. They won't take up much space, and the variations are too wide to put them in a shared folder
mockDateRange
it('should calculate the number of days in a range', () => {
// Inline mock object
const mockDateRange: DateRange = {
start: new Date('2025-01-01'),
end: new Date('2025-01-07')
};
// Calculate difference in days
const days = (mockDateRange.end.getTime() - mockDateRange.start.getTime())
/ (1000 * 60 * 60 * 24);
expect(days).toBe(6);
});
mockPagination
it('should move to the next page', () => {
// Inline mock object
const mockPagination: Pagination = {
page: 1,
pageSize: 10,
total: 100
};
// Create a new pagination object by updating only the page
const nextPage = { ...mockPagination, page: mockPagination.page + 1 };
expect(nextPage.page).toBe(2);
expect(nextPage.pageSize).toBe(10);
expect(nextPage.total).toBe(100);
});
For larger types like user-profile.model.ts and audit-log-entry.model.ts, we want them to be in the larger shared types folder.
├── shared
│ ├── components
│ │ ├── navbar/
│ │ └── sidebar/
│ ├── directives
│ │ └── debounce.directive.ts
│ ├── pipes
│ │ └── currency-format.pipe.ts
│ ├── models
│ │ └── api-response.ts
│ │ └── pagination.ts
│ │ └── date-range.ts
│ │ ├── user-profile.model.ts
│ │ ├── address.model.ts
│ │ └── audit-log-entry.model.ts
│ ├── mocks
│ │ └── user-profile.mock.ts
│ │ └── audit-log-entry.mock.ts
│ └── shared.module.ts
And declare these in their respective mock files like so:
user-profile.mock.ts
export const createMockUserProfile = (): UserProfile => ({
id: 'u-001',
firstName: 'Jane',
lastName: 'Doe',
email: 'jane.doe@example.com',
role: 'user',
isActive: true,
createdAt: new Date('2025-01-01T10:00:00Z'),
updatedAt: new Date('2025-01-05T15:30:00Z'),
} as UserProfile);
user-profile.mock.ts
// audit-log-entry.mock.ts
export const createMockAuditLogEntry = (): AuditLogEntry => ({
id: 'log-001',
userId: 'u-001',
action: 'CREATE',
entity: 'Product',
entityId: 'p-001',
timestamp: new Date('2025-01-10T12:00:00Z'),
metadata: { ipAddress: '192.168.1.1', browser: 'Chrome' },
} as AuditLogEntry);
Given we'll likely require multiple variations of this, there will be the necessity use the spread operator for these as we did earlier Jump note about spread
createMockAuditLogEntry produces a brand-new object, which makes it impossible for mutations in one test case to bleed into another. Also, we're using a type assertion in where you see } as AuditLogEntry) . This is because if we do decide we want to access an specific properties, we get the IntelliSense benefits of the IDE when searching for them.Now let's take what this looks like for an Nx Monorepo application.
Nx Monorepo Structure
Nx is a powerful build system and toolkit for managing monorepos — repositories that host multiple applications and libraries in one place. Instead of juggling separate repos for every app, service, or library, a monorepo lets you share code, enforce consistent standards, and scale large projects efficiently. Nx adds smart features like dependency graphs, computation caching, and generators to keep your workspace organized and fast. Each application inside the monorepo is still its own deployable unit, so builds and deployments remain targeted rather than all-or-nothing.
Here’s a sample structure for an enterprise-level application. Notice how this monorepo example stops at the folder level instead of drilling into individual files — that’s intentional, because complexity compounds quickly. All the models folders you see below make monorepos the perfect breeding ground for spec files packed with mock data. Just deciding where to put it all can feel like a pain. But if we apply the same thinking to a smaller, generic “non-monorepo” example, it doesn’t have to be rocket science.
my-workspace/
├── apps/
│ ├── web/
│ │ └── src/
│ ├── api/
│ │ └── src/
│ └── admin/
│ └── src/
│
├── libs/
│ ├── feature-orders/
│ │ ├── models/
│ │ ├── data-access/
│ │ ├── ui/
│ │ └── utils/
│ │
│ ├── feature-products/
│ │ ├── data-access/
│ │ │ └── models/
│ │ ├── ui/
│ │ ├── utils/
│ │ └── project.json
│ │
│ ├── shared/
│ │ ├── models/
│ │ ├── data-access/
│ │ ├── ui/
│ │ └── utils/
│ │
│ └── core/
│ ├── models/
│ ├── config/
│ ├── guards/
│ └── interceptors/
│
├── tools/
│
├── nx.json
├── package.json
└── tsconfig.base.json
Here’s our first example of a models folder inside the feature-orders feature. Unlike the higher-level monorepo structure we looked at earlier, this snippet drills down into actual files so you can see how everything lines up in practice.
If the use cases are fairly consistent — where the same mock data can be reused over and over — it makes sense to keep a mocks folder right alongside the models folder:
│ ├── feature-orders/
│ │ ├── models/
│ │ │ ├── order.model.ts
│ │ │ ├── order-item.model.ts
│ │ │ └── order-summary.model.ts
│ │ ├── mocks/
│ │ │ ├── order.mock.ts
│ │ │ ├── order-item.mock.ts
│ │ │ └── order-summary.mock.ts
│ │ ├── data-access/
│ │ │ ├── orders.service.ts
│ │ │ ├── orders.api.ts
│ │ │ └── orders.repository.ts
│ │ ├── ui/
│ │ │ ├── order-list.component.ts
│ │ │ ├── order-details.component.ts
│ │ │ └── order-form.component.ts
│ │ └── utils/
│ │ ├── order-helpers.ts
│ │ └── order-validation.ts
Our second example is the feature-products folder. It is very similar to feature-orders, but the difference here is the addition of a data-access directory. In Nx monorepos, data-access is where you put services and repositories that fetch, persist, or transform data.
A classic example in Angular would be HTTP services that talk to an API. Because each file in this directory usually only has one or two use cases, it makes sense to keep a mocks folder right next to the models folder:
│ ├── feature-products/
│ │ ├── data-access/
│ │ │ ├── products.service.ts
│ │ │ ├── products.api.ts
│ │ │ ├── products.repository.ts
│ │ │ ├── models/
│ │ │ │ ├── product.dto.ts
│ │ │ │ ├── category.dto.ts
│ │ │ │ └── inventory-status.dto.ts
│ │ │ └── mocks/
│ │ │ ├── product.dto.mock.ts
│ │ │ ├── category.dto.mock.ts
│ │ │ └── inventory-status.dto.mock.ts
│ │ ├── ui/
│ │ │ ├── product-list.component.ts
│ │ │ ├── product-details.component.ts
│ │ │ └── product-form.component.ts
│ │ ├── utils/
│ │ │ ├── product-formatter.ts
│ │ │ ├── price-calculator.ts
│ │ │ └── product-validators.ts
│ │ └── project.json
Next, in our shared folder, we are likely to see many instances and many variations of the listed shared module. We're going to only make seperate mock files for our larger interfaces and put them in the adjacent mocks folder, and update them with spread operator when needed like we did in our earlier example. Jump note about spread
│ ├── shared/
│ │ ├── models/
│ │ │ ├── pagination.model.ts
│ │ │ ├── date-range.model.ts
│ │ │ ├── api-response.model.ts
│ │ │ ├── error-state.model.ts
│ │ │ ├── user-profile.model.ts
│ │ │ ├── address.model.ts
│ │ │ └── audit-log-entry.model.ts
│ │ ├── mocks/
│ │ │ ├── user-profile.mock.ts
│ │ │ ├── address.mock.ts
│ │ │ └── audit-log-entry.mock.ts
│ │ ├── data-access/
│ │ │ ├── http-client.service.ts
│ │ │ ├── auth.interceptor.ts
│ │ │ └── caching-strategy.ts
│ │ ├── ui/
│ │ │ ├── button.component.ts
│ │ │ ├── modal.component.ts
│ │ │ └── spinner.component.ts
│ │ └── utils/
│ │ ├── date-formatter.ts
│ │ ├── currency-formatter.ts
│ │ └── string-helpers.ts
For the smaller models, it’s usually simpler to instantiate them directly in spec files on a case-by-case basis.
it('should create a valid DateRange object for the report filter', () => {
const dateRange: DateRange = {
start: new Date('2025-01-01T00:00:00Z'),
end: new Date('2025-01-31T23:59:59Z'),
};
expect(dateRange.start.getFullYear()).toBe(2025);
expect(dateRange.end.getMonth()).toBe(0); // January is 0
});
The core folder is typically used for application-wide, cross-cutting concerns. Since these models and services are referenced across the entire app, their mocks are often reused. Smaller models can still be instantiated directly in spec files, but larger ones benefit from a dedicated mocks folder.
│ └── core/
│ ├── models/
│ │ ├── user.model.ts
│ │ ├── auth-token.model.ts
│ │ ├── role.model.ts
│ │ └── system-settings.model.ts
│ ├── mocks/
│ │ ├── user.mock.ts
│ │ └── system-settings.mock.ts
│ ├── config/
│ │ ├── app-config.ts
│ │ ├── environment.ts
│ │ └── feature-flags.ts
│ ├── guards/
│ │ ├── auth.guard.ts
│ │ └── role.guard.ts
│ ├── interceptors/
│ │ ├── auth.interceptor.ts
│ │ └── error-logger.interceptor.ts
│ └── services/
│ ├── auth.service.ts
│ ├── logger.service.ts
│ └── config.service.ts
Wrapping it Up
Is it a little lazy to keep all your mocks inside the spec file where they’re used? Sure. Is it utterly lazy? Not really. Most of the time we’re in “build the component, then test it” mode, and pausing to make architectural calls isn’t where our focus naturally goes — especially if we didn’t shape the architecture in the first place.
That’s why having a couple of shorthand rules helps. They give you enough structure to make quick, consistent choices about where mocks belong, without needing to trace through the entire file tree.
Stay sharp with Angular testing insights, mock strategies, and dev landmine avoidance.