| |

Angular 100 🅰️ Testing — Unit Tests with Jasmine and Karma

⏱️ Estimated read: 17 min | |

Every Angular application that ships without tests is an application that ships with unknown defects. The tests are the specification. They describe what the code is supposed to do. When the code changes, the tests tell you whether the change broke something. When a new developer joins the team, the tests are the documentation of the intended behavior. Angular has a built-in testing infrastructure: Jasmine for the test syntax and assertions, and Karma for running the tests in a real browser.

The Angular CLI configures both by default. When you create a new project with ng new, the testing setup is already in place. The ng test command compiles the application, launches a browser, runs the tests, and reports the results. The tests are written in .spec.ts files next to the code they test. The naming convention is standard: counter.component.ts has a corresponding counter.component.spec.ts .

Key point: Angular’s unit tests run in a real browser through Karma. This is different from Node-based test runners like Jest or Vitest. The browser environment means the tests exercise the actual DOM, the actual rendering, and the actual change detection. The trade-off is speed: Karma tests are slower to start than Node tests because they launch a browser. The benefit is fidelity: the tests run in the environment the application runs in.


Why unit tests matter

A unit test verifies one piece of the application in isolation. It calls a method and checks the result. It renders a component and checks the DOM. It injects a service and checks its state. The test is fast, focused, and repeatable.

The regression problem. A developer changes a function that other code depends on. The change seems correct. The application is deployed. A feature that worked yesterday is broken today. Without tests, the break is discovered by a user. With tests, the break is discovered before the commit.

The refactoring problem. A developer needs to restructure a component. The behavior must remain the same. Without tests, the only way to verify the behavior is to test it manually. With tests, the behavior is verified automatically. The tests are the safety net that makes refactoring safe.

The specification problem. A requirement says “the counter increments when the button is clicked.” The test says the same thing in code. The test is the executable specification. When the requirement changes, the test changes. When the code changes, the test verifies that the requirement is still met.

The documentation problem. A new developer reads the test file and understands what the component does, what its inputs are, what its outputs are, and what the edge cases are. The test file is often more up-to-date than the documentation because the tests fail when the code changes.

The trade-off. Tests take time to write and maintain. A test suite that is slow or flaky becomes a burden. The team must invest in the tests and keep them reliable. The investment pays back in the confidence to change the code, the speed of the feedback loop, and the reduction in production defects.


a. The Testing Setup

The Angular CLI configures Jasmine and Karma when the project is created. The package.json includes jasmine-core, karma, karma-jasmine, karma-chrome-launcher, and karma-coverage. The angular.json file includes the test target with the @angular-devkit/build-angular:karma builder. The tsconfig.spec.json file includes the test files and the Jasmine types .

The test files are named with the .spec.ts extension and placed next to the code they test. The ng test command compiles the application, launches Chrome, runs the tests, and watches for changes. The --watch flag keeps the browser open and re-runs the tests on every change. The --code-coverage flag produces a coverage report .

A minimal test file has three parts: the describe block, the beforeEach block, and the it blocks.

describe('Calculator', () => {
  let calculator: Calculator;

  beforeEach(() => {
    calculator = new Calculator();
  });

  it('should add two numbers', () => {
    expect(calculator.add(2, 3)).toBe(5);
  });

  it('should subtract two numbers', () => {
    expect(calculator.subtract(5, 3)).toBe(2);
  });
});

The describe block groups related tests. The beforeEach block runs before every it block and sets up the test state. The it blocks are the individual tests. The expect function is the assertion .


b. Testing Components with TestBed

A component test uses the TestBed to configure a testing module. The TestBed is the Angular testing utility that provides the component’s dependencies and creates the component fixture .

The TestBed.configureTestingModule method takes the same configuration as an @NgModule. The declarations array lists the components and directives used in the test. The providers array lists the services. The imports array lists the modules .

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
  let component: CounterComponent;
  let fixture: ComponentFixture<CounterComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [CounterComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(CounterComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should increment the count when the button is clicked', () => {
    const button = fixture.nativeElement.querySelector('button');
    button.click();
    fixture.detectChanges();
    expect(component.count()).toBe(1);
  });
});

The compileComponents method compiles the component’s template and styles. The createComponent method creates the component and its fixture. The fixture.componentInstance is the component instance. The fixture.nativeElement is the component’s DOM element. The fixture.detectChanges method triggers change detection .

The fixture.nativeElement.querySelector method finds an element in the rendered DOM. The button.click() method simulates a click. The fixture.detectChanges() method updates the DOM after the click. The assertion checks the component’s state .


c. Testing Services and HTTP

A service test does not need the TestBed unless the service has dependencies. A service with no dependencies can be instantiated directly with new. A service with dependencies uses the TestBed to provide the dependencies.

import { TestBed } from '@angular/core/testing';
import { CalculatorService } from './calculator.service';

describe('CalculatorService', () => {
  let service: CalculatorService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(CalculatorService);
  });

  it('should add two numbers', () => {
    expect(service.add(2, 3)).toBe(5);
  });
});

The TestBed.inject method retrieves the service instance from the injector. The service is instantiated with its dependencies .

A service that makes HTTP requests uses the HttpClientTestingModule and the HttpTestingController. The HttpTestingController intercepts the HTTP requests and allows the test to provide mock responses .

import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
      ],
    });
    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should fetch users', () => {
    const mockUsers = [{ id: 1, name: 'Alice' }];

    service.getUsers().subscribe((users) => {
      expect(users).toEqual(mockUsers);
    });

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);
  });
});

The provideHttpClient and provideHttpClientTesting providers set up the HTTP client with the testing backend. The httpMock.expectOne method asserts that a request was made to the specified URL. The req.flush method provides the mock response. The httpMock.verify method asserts that no unexpected requests were made .


d. Spies and Mocks

Jasmine provides the spyOn function to replace a method with a spy. The spy records the calls and allows the test to provide a return value.

describe('UserService', () => {
  let service: UserService;
  let httpSpy: jasmine.Spy;

  beforeEach(() => {
    httpSpy = jasmine.createSpy('get').and.returnValue(of([]));
    service = new UserService({ get: httpSpy } as any);
  });

  it('should call the http client', () => {
    service.getUsers();
    expect(httpSpy).toHaveBeenCalledWith('/api/users');
  });
});

The jasmine.createSpy method creates a spy function. The and.returnValue method sets the return value. The toHaveBeenCalledWith matcher asserts that the spy was called with the specified arguments .

A spy can also be attached to an existing object with spyOn:

const userService = TestBed.inject(UserService);
spyOn(userService, 'getUsers').and.returnValue(of([]));

The spyOn method replaces the method with a spy. The original method is not called unless and.callThrough() is used .


Complete Example Session

This session tests a counter component, a calculator service, and an HTTP service.

// ============================================
// PART 1: THE COUNTER COMPONENT
// ============================================

// counter.component.ts
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <p>Count: {{ count() }}</p>
    <button (click)="increment()">+</button>
    <button (click)="decrement()">−</button>
    <button (click)="reset()">Reset</button>
  `,
})
export class CounterComponent {
  count = signal(0);

  increment(): void {
    this.count.update((v) => v + 1);
  }

  decrement(): void {
    this.count.update((v) => v - 1);
  }

  reset(): void {
    this.count.set(0);
  }
}

// ============================================
// PART 2: THE COUNTER COMPONENT TEST
// ============================================

// counter.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
  let component: CounterComponent;
  let fixture: ComponentFixture<CounterComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [CounterComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(CounterComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create the component', () => {
    expect(component).toBeTruthy();
  });

  it('should start with count 0', () => {
    expect(component.count()).toBe(0);
  });

  it('should increment the count', () => {
    component.increment();
    expect(component.count()).toBe(1);
  });

  it('should decrement the count', () => {
    component.decrement();
    expect(component.count()).toBe(-1);
  });

  it('should reset the count', () => {
    component.increment();
    component.increment();
    component.reset();
    expect(component.count()).toBe(0);
  });

  it('should increment when the + button is clicked', () => {
    const buttons = fixture.nativeElement.querySelectorAll('button');
    buttons[0].click();
    fixture.detectChanges();
    expect(component.count()).toBe(1);
  });

  it('should display the count in the template', () => {
    component.increment();
    fixture.detectChanges();
    const p = fixture.nativeElement.querySelector('p');
    expect(p.textContent).toContain('Count: 1');
  });
});

// ============================================
// PART 3: THE CALCULATOR SERVICE
// ============================================

// calculator.service.ts
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CalculatorService {
  add(a: number, b: number): number {
    return a + b;
  }

  subtract(a: number, b: number): number {
    return a - b;
  }

  multiply(a: number, b: number): number {
    return a * b;
  }

  divide(a: number, b: number): number {
    if (b === 0) {
      throw new Error('Division by zero');
    }
    return a / b;
  }
}

// ============================================
// PART 4: THE CALCULATOR SERVICE TEST
// ============================================

// calculator.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { CalculatorService } from './calculator.service';

describe('CalculatorService', () => {
  let service: CalculatorService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(CalculatorService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should add two numbers', () => {
    expect(service.add(2, 3)).toBe(5);
  });

  it('should subtract two numbers', () => {
    expect(service.subtract(5, 3)).toBe(2);
  });

  it('should multiply two numbers', () => {
    expect(service.multiply(4, 5)).toBe(20);
  });

  it('should divide two numbers', () => {
    expect(service.divide(10, 2)).toBe(5);
  });

  it('should throw when dividing by zero', () => {
    expect(() => service.divide(10, 0)).toThrowError('Division by zero');
  });
});

// ============================================
// PART 5: THE USER SERVICE WITH HTTP
// ============================================

// user.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface User {
  id: number;
  name: string;
}

@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users');
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`/api/users/${id}`);
  }

  createUser(user: Omit<User, 'id'>): Observable<User> {
    return this.http.post<User>('/api/users', user);
  }
}

// ============================================
// PART 6: THE USER SERVICE TEST
// ============================================

// user.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { UserService, User } from './user.service';

describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
      ],
    });
    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should fetch users', () => {
    const mockUsers: User[] = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
    ];

    service.getUsers().subscribe((users) => {
      expect(users.length).toBe(2);
      expect(users).toEqual(mockUsers);
    });

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);
  });

  it('should fetch a single user', () => {
    const mockUser: User = { id: 1, name: 'Alice' };

    service.getUser(1).subscribe((user) => {
      expect(user).toEqual(mockUser);
    });

    const req = httpMock.expectOne('/api/users/1');
    expect(req.request.method).toBe('GET');
    req.flush(mockUser);
  });

  it('should create a user', () => {
    const newUser = { name: 'Carol' };
    const createdUser: User = { id: 3, name: 'Carol' };

    service.createUser(newUser).subscribe((user) => {
      expect(user).toEqual(createdUser);
    });

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('POST');
    expect(req.request.body).toEqual(newUser);
    req.flush(createdUser);
  });

  it('should handle a 404 error', () => {
    service.getUser(999).subscribe({
      next: () => fail('Expected an error'),
      error: (error) => {
        expect(error.status).toBe(404);
      },
    });

    const req = httpMock.expectOne('/api/users/999');
    req.flush('Not found', { status: 404, statusText: 'Not Found' });
  });
});

// ============================================
// PART 7: THE SPY EXAMPLE
// ============================================

// notification.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { NotificationService } from './notification.service';
import { UserService } from './user.service';
import { of } from 'rxjs';

describe('NotificationService', () => {
  let service: NotificationService;
  let userService: jasmine.SpyObj<UserService>;

  beforeEach(() => {
    const spy = jasmine.createSpyObj('UserService', ['getUsers']);
    TestBed.configureTestingModule({
      providers: [
        NotificationService,
        { provide: UserService, useValue: spy },
      ],
    });
    service = TestBed.inject(NotificationService);
    userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
  });

  it('should notify active users', () => {
    userService.getUsers.and.returnValue(of([{ id: 1, name: 'Alice' }]));
    service.notifyAll();
    expect(userService.getUsers).toHaveBeenCalled();
  });
});

// ============================================
// PART 8: THE ASYNC TEST
// ============================================

// async.service.spec.ts
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { AsyncService } from './async.service';

describe('AsyncService', () => {
  let service: AsyncService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(AsyncService);
  });

  it('should resolve after a delay', fakeAsync(() => {
    let result: string | undefined;
    service.getDelayedValue().then((value) => (result = value));
    tick(1000);
    expect(result).toBe('done');
  }));
});

// ============================================
// PART 9: THE TEST COMMANDS
// ============================================

// Run all tests
// ng test

// Run once and exit
// ng test --watch=false

// Run with code coverage
// ng test --code-coverage

// Run a specific test file
// ng test --include='**/counter.component.spec.ts'

// ============================================
// PART 10: THE TESTING CHECKLIST
// ============================================

// 1. Write tests for every component
// 2. Write tests for every service
// 3. Test the happy path
// 4. Test the edge cases
// 5. Test the error cases
// 6. Use TestBed for components with dependencies
// 7. Use HttpTestingController for HTTP
// 8. Use spies for external dependencies
// 9. Use fakeAsync for timers
// 10. Run the tests in CI

The ten parts cover the counter component, the counter component test, the calculator service, the calculator service test, the user service with HTTP, the user service test, the spy example, the async test, the test commands, and the testing checklist.


Quick Reference

The Jasmine Functions

FunctionPurpose
describeGroup related tests
itDefine a single test
beforeEachRun before each test
afterEachRun after each test
beforeAllRun once before all tests
afterAllRun once after all tests
expectAssert a value
failForce a test failure
spyOnReplace a method with a spy
jasmine.createSpyCreate a standalone spy
jasmine.createSpyObjCreate an object with spy methods

The Jasmine Matchers

MatcherPurpose
toBeStrict equality
toEqualDeep equality
toBeTruthyTruthy value
toBeFalsyFalsy value
toContainContains a value
toThrowErrorThrows an error
toHaveBeenCalledSpy was called
toHaveBeenCalledWithSpy was called with args

The TestBed Methods

MethodPurpose
configureTestingModuleConfigure the test module
compileComponentsCompile the template
createComponentCreate the component
injectRetrieve a service
overrideProviderOverride a provider

The ComponentFixture Properties

PropertyPurpose
componentInstanceThe component instance
nativeElementThe DOM element
debugElementThe debug element
detectChangesTrigger change detection
whenStableWait for async stability

The HTTP Testing

MethodPurpose
provideHttpClient()Provide the HTTP client
provideHttpClientTesting()Provide the testing backend
httpMock.expectOne(url)Assert one request
httpMock.expectNone(url)Assert no request
req.flush(data)Provide a response
req.error(error)Provide an error
httpMock.verify()Assert no pending requests

Best Practices

✅ Do This:

// Use beforeEach to set up the state
beforeEach(async () => {
  await TestBed.configureTestingModule({ imports: [CounterComponent] }).compileComponents();
});                                                              // ✅
// Call detectChanges after state changes
component.increment();
fixture.detectChanges();                                         // ✅
// Use HttpTestingController for HTTP
const req = httpMock.expectOne('/api/users');
req.flush(mockUsers);                                            // ✅
// Verify no pending HTTP requests
afterEach(() => httpMock.verify());                              // ✅
// Use spies for external dependencies
spyOn(userService, 'getUsers').and.returnValue(of([]));          // ✅

❌ Don’t Do This:

// Don't test implementation details
expect(component['privateMethod']()).toBe(true);                 // ❌
// Don't forget to flush HTTP requests
httpMock.expectOne('/api/users');  // no flush → test hangs      // ❌
// Don't use real HTTP in unit tests
http.get('/api/users');  // use HttpTestingController            // ❌
// Don't rely on test order
// Each test should be independent.                              // ❌

Common Pitfalls

PitfallWhy It HappensFix
Test hangsHTTP request not flushedUse req.flush()
Change detection not runningdetectChanges not calledCall fixture.detectChanges()
Service not foundProvider not configuredAdd to providers
Async test failsPromise not awaitedUse fakeAsync and tick
Test depends on orderShared stateReset in beforeEach
Coverage lowTests missingAdd tests for edge cases

Real-World Examples

1. Component Test

fixture = TestBed.createComponent(CounterComponent);
component = fixture.componentInstance;

2. DOM Query

const button = fixture.nativeElement.querySelector('button');

3. Click Simulation

button.click();
fixture.detectChanges();

4. Service Test

service = TestBed.inject(CalculatorService);

5. HTTP Test

const req = httpMock.expectOne('/api/users');
req.flush(mockUsers);

6. Spy

spyOn(userService, 'getUsers').and.returnValue(of([]));

7. Spy Object

const spy = jasmine.createSpyObj('UserService', ['getUsers']);

8. Async Test

it('should resolve', fakeAsync(() => {
  let result;
  service.getValue().then(v => result = v);
  tick(1000);
  expect(result).toBe('done');
}));

9. Error Test

expect(() => service.divide(10, 0)).toThrowError('Division by zero');

10. Run Tests

ng test --watch=false --code-coverage

Visual

The Testing Pyramid

┌──────────────────────────────────────────────┐
│  TESTING PYRAMID                             │
│                                              │
│         E2E                                  │
│        (few)                                 │
│       ───────                                │
│      Integration                             │
│      (some)                                  │
│     ───────────                              │
│    Unit Tests                                │
│    (many)                                    │
│                                              │
│  Unit tests are fast and focused.            │
│  Integration tests verify the wiring.        │
│  E2E tests verify the whole application.     │
│                                              │
└──────────────────────────────────────────────┘

The Test Lifecycle

┌──────────────────────────────────────────────┐
│  TEST LIFECYCLE                              │
│                                              │
│  describe('Component')                       │
│    │                                         │
│    ├─ beforeAll()                            │
│    │                                         │
│    ├─ beforeEach()                           │
│    │    └─ Set up the state                  │
│    │                                         │
│    ├─ it('should do something')              │
│    │    └─ Test body                         │
│    │                                         │
│    ├─ afterEach()                            │
│    │    └─ Clean up                          │
│    │                                         │
│    ├─ beforeEach()                           │
│    ├─ it('should do another thing')          │
│    │                                         │
│    └─ afterAll()                             │
│                                              │
└──────────────────────────────────────────────┘

The HTTP Testing Flow

┌──────────────────────────────────────────────┐
│  HTTP TESTING FLOW                           │
│                                              │
│  service.getUsers().subscribe(...)           │
│       │                                      │
│       ▼                                      │
│  httpMock.expectOne('/api/users')            │
│       │                                      │
│       ▼                                      │
│  req.flush(mockUsers)                        │
│       │                                      │
│       ▼                                      │
│  The subscriber receives the data            │
│       │                                      │
│       ▼                                      │
│  httpMock.verify()                           │
│                                              │
│  The HttpTestingController intercepts the    │
│  request and provides the mock response.     │
│                                              │
└──────────────────────────────────────────────┘

The TestBed Configuration

┌──────────────────────────────────────────────┐
│  TESTBED CONFIGURATION                       │
│                                              │
│  TestBed.configureTestingModule({            │
│    imports: [CounterComponent],              │
│    providers: [UserService],                 │
│  }).compileComponents();                     │
│                                              │
│  fixture = TestBed.createComponent(CounterComponent)│
│  component = fixture.componentInstance       │
│  fixture.detectChanges()                     │
│                                              │
│  The TestBed provides the dependencies.      │
│  The fixture wraps the component.            │
│  The detectChanges triggers rendering.       │
│                                              │
└──────────────────────────────────────────────┘

Summary

ItemValue
Test frameworkJasmine
Test runnerKarma
Test files*.spec.ts
Test commandng test
Component testingTestBed, ComponentFixture
DOM queryfixture.nativeElement.querySelector
Change detectionfixture.detectChanges()
Service testingTestBed.inject
HTTP testingHttpTestingController
SpiesspyOn, jasmine.createSpy, jasmine.createSpyObj
Async testingfakeAsync, tick, whenStable

Key takeaways:

  • Angular’s testing infrastructure is Jasmine and Karma. Jasmine provides the test syntax and assertions. Karma runs the tests in a real browser. The CLI configures both by default. The ng test command runs the tests .
  • The TestBed configures the testing module. The configureTestingModule method takes the same configuration as an @NgModule. The compileComponents method compiles the template. The createComponent method creates the component and its fixture .
  • The ComponentFixture provides access to the component and its DOM. The componentInstance is the component. The nativeElement is the DOM element. The detectChanges method triggers change detection. The whenStable method waits for async stability .
  • The HttpTestingController intercepts HTTP requests. The expectOne method asserts that a request was made. The flush method provides the response. The verify method asserts that no unexpected requests were made .
  • Jasmine spies replace methods with controllable implementations. The spyOn method replaces a method on an existing object. The jasmine.createSpy method creates a standalone spy. The jasmine.createSpyObj method creates an object with spy methods .
  • Async tests use fakeAsync and tick. The fakeAsync function wraps the test in a fake timer context. The tick function advances the virtual clock. The promise callbacks run synchronously within the fake context .
  • The tests are the specification. They describe the intended behavior. They catch regressions. They document the component’s API. They make refactoring safe. A codebase without tests is a codebase with unknown defects.

Remember: Testing is not optional. Every component, every service, every HTTP call, every edge case should have a test. The TestBed provides the infrastructure. The ComponentFixture provides the component. The HttpTestingController provides the HTTP. The spies provide the mocks. The fakeAsync provides the time control. The tests run in a real browser through Karma. The ng test command runs them. Write the tests. Run them. Keep them green.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!