Модуль 8 · урок 4 з 9
Тест пайпа й guard
Два з пʼяти тестів, які просить лід. Обидва прості — тому з них і варто починати: перший зелений тест знімає психологічний барʼєр краще за будь-яку теорію.
Тест пайпа
// due-in.pipe.ts
@Pipe({ name: 'dueIn' })
export class DueInPipe implements PipeTransform {
transform(value: string | Date | null, now: Date = new Date()): string {
if (!value) return 'без терміну';
const due = new Date(value);
const days = Math.round((due.getTime() - now.getTime()) / 86_400_000);
if (days < 0) return `прострочено на ${Math.abs(days)} дн.`;
if (days === 0) return 'сьогодні';
if (days === 1) return 'завтра';
return `через ${days} дн.`;
}
}
// due-in.pipe.spec.ts
describe('DueInPipe', () => {
const pipe = new DueInPipe(); // жодного TestBed
const now = new Date('2026-09-09T12:00:00');
it('повертає підказку для порожнього значення', () => {
expect(pipe.transform(null, now)).toBe('без терміну');
});
it('розпізнає сьогодні', () => {
expect(pipe.transform('2026-09-09T18:00:00', now)).toBe('сьогодні');
});
it('розпізнає завтра', () => {
expect(pipe.transform('2026-09-10T12:00:00', now)).toBe('завтра');
});
it('рахує прострочення', () => {
expect(pipe.transform('2026-09-04T12:00:00', now)).toBe('прострочено на 5 дн.');
});
});
now — не примха
Якби пайп брав new Date() усередині, тест довелось би або привʼязувати
до поточної дати (і він зламався б завтра), або підміняти системний час.
Параметр зі значенням за замовчуванням робить функцію передбачуваною й не ускладнює
використання: у шаблоні пишеться так само {{ date | dueIn }}.
Це загальне правило: усе, що приходить ззовні — час, випадковість, поточний користувач — краще передавати параметром. Тоді тест стає тривіальним.
Тест guard: спершу чиста функція
// логіка окремо — рівно та ката з модуля 2
export function decideAccess(user: User | null, required?: string): true | string {
if (!user) return '/login';
if (!required) return true;
if (user.roles.includes('admin')) return true;
return user.roles.includes(required) ? true : '/forbidden';
}
describe('decideAccess', () => {
it('неавторизованого веде на логін', () => {
expect(decideAccess(null, 'producer')).toBe('/login');
});
it('пускає, коли роль не потрібна', () => {
expect(decideAccess({ roles: [] }, undefined)).toBe(true);
});
it('пускає з потрібною роллю', () => {
expect(decideAccess({ roles: ['producer'] }, 'producer')).toBe(true);
});
it('не пускає без ролі', () => {
expect(decideAccess({ roles: ['artist'] }, 'producer')).toBe('/forbidden');
});
it('admin проходить будь-куди', () => {
expect(decideAccess({ roles: ['admin'] }, 'producer')).toBe(true);
});
});
Пʼять тестів, жодного Angular, виконуються миттєво. Саме так виглядає користь від того, що в модулі 2 логіка була винесена з guard у чисту функцію.
Тест самого guard
describe('roleGuard', () => {
let auth: jasmine.SpyObj<AuthService>;
let router: jasmine.SpyObj<Router>;
beforeEach(() => {
auth = jasmine.createSpyObj<AuthService>('AuthService', ['roles']);
router = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
TestBed.configureTestingModule({
providers: [
{ provide: AuthService, useValue: auth },
{ provide: Router, useValue: router },
],
});
});
function run(route: { data: Record<string, unknown> }) {
// функціональний guard треба викликати в контексті інʼєкції
return TestBed.runInInjectionContext(() =>
roleGuard(route as ActivatedRouteSnapshot, {} as RouterStateSnapshot),
);
}
it('пускає користувача з потрібною роллю', () => {
auth.roles.and.returnValue(['producer']);
expect(run({ data: { role: 'producer' } })).toBe(true);
});
it('редіректить без ролі', () => {
auth.roles.and.returnValue(['artist']);
router.createUrlTree.and.returnValue({} as UrlTree);
run({ data: { role: 'producer' } });
expect(router.createUrlTree).toHaveBeenCalledWith(['/forbidden']);
});
});
runInInjectionContext
Функціональний guard усередині викликає inject(), а той працює лише
в контексті інʼєкції. У тесті цей контекст створюється саме так. Без нього отримаєш
помилку «inject() must be called from an injection context» — і це найчастіша перешкода
при першому тесті guard.
- Межі: порожнє значення, нуль, вчора й завтра.
- Кожну гілку умови окремим тестом.
- Що редірект іде саме туди, куди треба.
- Перевіряти формат рядка, який завтра змінить дизайнер.
- Дублювати той самий сценарій з різними числами.
- Тестувати сам Angular: що
injectпрацює.