≡ Menu

Running unit tests against a live production database is an anti-pattern that leads to data corruption, slow pipelines, and unreliable test states. Instead, the gold standard is to test your backend services in complete isolation.

Because NestJS is built entirely around Dependency Injection (DI), we can easily intercept its architecture and swap out heavy database connections for lightweight, lightning-fast “mocks.”

In this tutorial, we will break down exactly how to mock TypeORM repositories inside a NestJS unit test using Jest.

The Complete Test Blueprint

Here is the complete unit test file for a standard UsersService. We are going to dissect this file block by block to see how it intercepts NestJS and fakes TypeORM seamlessly.

import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UsersService } from './users.service';
import { User } from './user.entity';
import { NotFoundException } from '@nestjs/common';

// 1. Define the mock repository blueprint
type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;

const createMockRepository = <T = any>(): MockRepository<T> => ({
  findOne: jest.fn(),
  save: jest.fn(),
});

describe('UsersService', () => {
  let service: UsersService;
  let userRepository: MockRepository<User>;

  beforeEach(async () => {
    // 2. Hijack the NestJS Dependency Injection System
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: getRepositoryToken(User),
          useValue: createMockRepository(),
        },
      ],
    }).compile();

    // 3. Extract instances from the test bed
    service = module.get<UsersService>(UsersService);
    userRepository = module.get<MockRepository<User>>(getRepositoryToken(User));
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('findOne', () => {
    // 4. Test Case 1: The "Happy Path" (User Found)
    it('should return a user if found', async () => {
      const mockUser = { id: 1, name: 'Alice', email: 'alice@example.com' };
      
      userRepository.findOne.mockResolvedValue(mockUser);

      const result = await service.findOne(1);
      
      expect(result).toEqual(mockUser);
      expect(userRepository.findOne).toHaveBeenCalledWith({ where: { id: 1 } });
    });

    // 5. Test Case 2: The "Sad Path" (User Not Found)
    it('should throw a NotFoundException if user is not found', async () => {
      userRepository.findOne.mockResolvedValue(null);

      await expect(service.findOne(1)).rejects.toThrow(NotFoundException);
    });
  });
});

Detailed Walkthrough

1. Creating the Fake Database Blueprint

TypeORM’s real Repository class contains dozens of methods (find, update, delete, etc.). We don’t want to manually mock all of them.

type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;

const createMockRepository = <T = any>(): MockRepository<T> => ({
  findOne: jest.fn(),
  save: jest.fn(),
});
  • The Type Definition: This uses TypeScript utility types (Partial and Record) to dynamically scan TypeORM’s real repository. It tells TypeScript: “Create an object where any method from TypeORM is optional, but if I use it, turn it into a Jest spy (jest.Mock).” This keeps things type-safe and gives you full autocomplete without manual boilerplate.

  • createMockRepository: This helper factory function returns a clean mock object for our tests. It maps the common database methods to jest.fn(), which are blank tracking spies that we can program to return custom values on the fly.

2. Hijacking NestJS Dependency Injection (beforeEach)

Before every single test case runs, we need to spin up a mini, isolated version of the NestJS runtime environment and intercept the database connection.

const module: TestingModule = await Test.createTestingModule({
  providers: [
    UsersService,
    {
      provide: getRepositoryToken(User),
      useValue: createMockRepository(),
    },
  ],
}).compile();
  • Test.createTestingModule: This builds a sandbox environment mimicking a real NestJS app module.

  • getRepositoryToken(User): In a real app, NestJS uses a hidden internal token to identify repositories injected via @InjectRepository(User). This function gets that exact token.

  • useValue: This tells NestJS: “Whenever UsersService asks for the real database repository using that token, do not load TypeORM. Inject our fake createMockRepository() object instead.”

3. Pulling Instances Out of the Test Bed

Once our sandbox module compiles, we need to grab the instantiated objects so we can manipulate them inside individual tests.

service = module.get<UsersService>(UsersService);
userRepository = module.get<MockRepository<User>>(getRepositoryToken(User));

We extract both the UsersService (which now holds our fake database under the hood) and the raw userRepository mock wrapper so we can tell it how to behave.

4. Test Case 1: The “Happy Path” (User Found)

Now we can safely test how our service handles a successful database retrieval.

it('should return a user if found', async () => {
  const mockUser = { id: 1, name: 'Alice', email: 'alice@example.com' };
  
  // 1. Tell the mock what to return
  userRepository.findOne.mockResolvedValue(mockUser);

  // 2. Execute the actual service code
  const result = await service.findOne(1);
  
  // 3. Assertions
  expect(result).toEqual(mockUser);
  expect(userRepository.findOne).toHaveBeenCalledWith({ where: { id: 1 } });
});
  • mockResolvedValue(mockUser): We explicitly command our mock repository: “The next time the service runs findOne, simulate a successful database promise resolution with this fake Alice JSON object.”

  • The Assertions: We verify that the service processes that database data properly and returns it intact. We also use toHaveBeenCalledWith to ensure our service actually passed the correct SQL parameters ({ where: { id: 1 } }) to the database driver.

5. Test Case 2: The “Sad Path” (User Not Found)

Good unit tests always check what happens when things go wrong. Here, we ensure our app correctly errors out if a user doesn’t exist.

it('should throw a NotFoundException if user is not found', async () => {
  // 1. Tell the mock to pretend the database returned nothing
  userRepository.findOne.mockResolvedValue(null);

  // 2. Assert that the service crashes with the correct NestJS HTTP exception
  await expect(service.findOne(1)).rejects.toThrow(NotFoundException);
});
  • mockResolvedValue(null): We simulate a database query that returns empty-handed.

  • rejects.toThrow(NotFoundException): This proves that your UsersService custom error handling handles null correctly. If the database returns nothing, it interrupts the flow and throws NestJS’s built-in NotFoundException, which automatically translates to a clean 404 Not Found HTTP status code on your frontend application.

Conclusion

By isolating your service layer from the actual database driver using this pattern, your unit tests will execute in milliseconds. They become entirely predictable, completely safe, and run perfectly across local development machines and automated CI/CD pipelines without requiring any database access strings or network connections.

Coding Quote of the Day:

“If it’s worth building, it’s worth testing. If it’s not worth testing, why are you wasting your time building it?” — Scott Ambler

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }

When you go headless, Shopify becomes an invisible engine that powers your shopping cart, inventory logic, and secure checkout.

Your actual frontend website is built completely from scratch using modern web engineering principles.

While headless offers incredible capabilities, it isn’t a magic bullet for every brand. Let’s break down exactly who this architecture is for, who should strictly avoid it, and why the combination of Next.js, Vercel, and Sanity has become the gold standard for high-growth stores.

1. The Core Assessment: Who Headless Is For (and Who It Isn’t)

Headless commerce is powerful, but it introduces an operational surface area that can easily choke a small business.

Before investing months of engineering time, look honestly at your store’s size, goals, and resources.

Who It Is For

  • High-Volume, Design-Forward Brands: Brands where storytelling, immersive branding, and custom micro-interactions directly drive revenue. If your design team frequently hears “we can’t build that in a standard Shopify theme,” you are a candidate.

  • Content-Heavy Retailers: Stores that sell through deep educational content, editorial blogs, recipes, or interactive tools.

  • Omnichannel Ecosystems: If you need to distribute the exact same product catalog and pricing logic across a web store, a native iOS/Android mobile app, and a physical point-of-sale terminal.

Who It Is NOT For

  • Early-Stage Startups (<$2M – $5M GMV): If you are still establishing product-market fit or have a small inventory, headless is a massive, expensive distraction. A premium Shopify Liquid theme with minimal apps can easily carry you past your first few million in revenue.

  • Teams Without Dedicated Developers: Once you slice off Shopify’s frontend, you can no longer click “Install App” in the Shopify App Store to automatically inject new UI elements into your site. If you don’t have engineers to maintain the code, do not go headless.

2. The Inevitable Tradeoffs

No architecture comes without concessions. Going headless means trading off standard convenience for absolute performance and control.

Feature / Metric Traditional Shopify (Liquid) Headless Shopify (Next.js + Sanity)
Page Load Speed Highly variable; bogs down easily with apps and tracking scripts. Sub-second; static files are compiled and delivered globally via CDN.
Design Flexibility Locked to a structured theme layout grid. 100% infinite freedom; you build every individual pixel from scratch.
App Store Ecosystem 1-click installations for UI widgets, pop-ups, and review systems. Requires developers to integrate apps via API or frontend SDKs.
Development Cost Low to moderate setup and maintenance fees. High initial investment; requires continuous professional engineering.

3. The Ultimate Frontend Duo: Next.js & Vercel

If you commit to going headless, your choice of frontend framework determines both your conversion rate and developer velocity. The industry overwhelmingly favors Next.js hosted on Vercel.

User Request ──> Vercel Edge CDN (Instant HTML) ──> Next.js App Router (Streaming Data)

Why Next.js is Built for Commerce

Next.js leverages modern React Server Components (RSC). In older web frameworks, the user’s browser had to download huge amounts of JavaScript to construct the webpage on their device, killing mobile load times.

With Next.js, the heavy rendering computation happens entirely on the server.

When a customer navigates to a product page, the server streams pre-built, optimized static HTML directly to their device.

  • Incremental Static Regeneration (ISR): You can pre-render 10,000 product pages at build time. If a merchant changes a product description or price, Next.js updates that specific page quietly in the background without requiring a full site rebuild.

  • Search Engine Optimization (SEO): Because Next.js serves fully structured HTML out of the box, search engine crawlers index your product pages effortlessly, boosting your organic search visibility.

The Vercel Advantage

Deploying Next.js on Vercel means your storefront is hosted on a world-class, multi-cloud global Edge Network.

Your pages are cached and served from data centers physically closest to your users, dropping Time to First Byte (TTFB) to milliseconds.

Vercel automatically manages image optimization, analytics telemetry, and branch previews for your engineering team.

4. Sanity: The Missing Editorial Brain

Standard Shopify handles operational data (SKUs, inventory counts, shipping weights) brilliantly. Where it struggles is content modeling—structuring rich storytelling elements, shoppable lookbooks, or modular landing pages.

This is where Sanity steps in as your headless CMS.

[Shopify Engine]  ──(Inventory Data)──> [Sanity Content Lake]
                                                │
                                       (Enriched Schema)
                                                ▼
                                        [Next.js Frontend]

Content as Structured Data

Sanity doesn’t treat pages as static rich-text boxes. It treats your content as structured JSON data inside a globally scaled Content Lake. This allows you to effortlessly sync Shopify’s standard product objects directly into Sanity using Sanity Connect.

Once synced, your marketing team can attach rich editorial schemas to a product document: adding recipe components, instructional videos, sizing calculators, or styling lookbooks that link directly to other matching products.

Sanity Visual Editing

The oldest complaint about headless stores is that marketing teams lose the ability to see what they are building.

Sanity eliminates this pain point entirely via Visual Editing and Presentation tools.

When wired up to your Next.js frontend preview environment, editors get an interactive preview pane.

They can click an image or text block directly on the live frontend preview, and Sanity Studio will instantly scroll to and highlight the exact field inside the backend editing dashboard.

The Verdict

Going headless is an investment in your storefront’s long-term scale and brand equity.

If your business generates the volume to justify a dedicated engineering workflow, stacking Next.js, Vercel, and Sanity on top of Shopify yields the ultimate modern retail platform: a secure, lightning-fast storefront optimized for maximum conversion and endless design iteration.

Coding Quote of the Day:

“The most disastrous thing that you can ever learn is your first programming language.” Alan Kay

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }