For future students on the course "JavaScript QA Engineer" and everyone interested in the topic of test automation, we prepared a translation of a useful article.
We also invite you to take part in the open webinar on the topic "What a tester needs to know about JS" . In the lesson, the participants, together with an expert, will consider the features of JS that need to be kept in mind when writing tests.
Unit tests are great ... when they work reliably! In fact, there is an old adage that "a bad test is worse than no test at all." I can confirm that weeks spent chasing an accidental "false negative" test are not effective. Instead, this time could be used to write working code to help the user.
: .
, , , .
,
( )
.
?
— , . . , « », "Gang Of Four's Design Pattern" . .
, , .
:
interface ISomeObj {
percentage: string;
}
export const makeSomeObj = () => {
return {
percentage: Math.random()
};
}
, , .
, , .
,
. , - . JSON-. Cypress ( ), JSON . , . JSON .
, . , , , .
// This file is "src/pages/newYorkInfo.tsx"
import * as React from 'react';
interface IUser {
state: string;
address: string;
isAdmin: boolean;
deleted: boolean | undefined;
}
export const NewYorkUserPage: React.FunctionComponent<{ user: IUser }> = props => {
if (props.user.state === 'NY' && !props.user.deleted) {
const welcomeMessage = `Welcome`;
return <h1 id="ny-dashboard">{welcomeMessage}</h1>;
} else {
return <div>ACCESS DENIED</div>;
}
};
, JSON .
// fixtures/user.json
{
state: 'NY',
isAdmin: true,
address: '55 Main St',
}
. , - psuedo- Cypress, , , .
// When the UI calls the user endpoint, return the JSON as the mocked return value
cy.route('GET', '/user/**', 'fixture:user.json');
cy.visit('/dashboard');
cy.get('#ny-dashboard').should('exist')
, , . ?
— , JSON-
JSON- ? , , (). , JSON-. 52 JSON-, . , , 104 . !
. , Product Owner : « , ».
, name
.
// This file is "src/pages/newYorkInfo.tsx"
import * as React from 'react';
interface IUser {
name: string;
state: string;
address: string;
isAdmin: boolean;
deleted: boolean | undefined;
}
export const NewYorkUserPage: React.FunctionComponent<{ user: IUser }> = props => {
if (props.user.state === 'NY' && !props.user.deleted) {
const welcomeMessage = `Welcome ${props.user.name.toLowerCase()}!`;
return <h1 id="ny-dashboard">{welcomeMessage}</h1>;
} else {
return <div>ACCESS DENIED</div>;
}
};
, , JSON . JSON name
, :
Uncaught TypeError: Cannot read property 'toLowerCase' of undefined
name 52 JSON . Typescript.
: TypeScript
JSON .ts , Typescript :
// this file is "testData/users"
import {IUser} from 'src/pages/newYorkInfo';
// Property 'name' is missing in type '{ state: string; isAdmin: true; address: string; deleted: false; }' but required in type 'IUser'.ts(2741)
export const generalUser: IUser = {
state: 'NY',
isAdmin: true,
address: '55 Main St',
deleted: false,
};
, .
import { generalUser } from 'testData/users';
// When the UI calls the user endpoint, return the JSON as the mocked return value
cy.route('GET', '/user/**', generalUser);
cy.visit('/dashboard');
cy.get('#ny-dashboard').should('exist')
Typescript! , name: 'Bob Smith'
GeneralUser:
, , , !
, . , .
, , , -. , , , , . deleted: false
generalUser
.
! , . .
( ) , . , ( ) deletedUser
, 1 . - — 5000 .
, .
// this file is "testData/users"
import {IUser} from 'src/pages/newYorkInfo';
export const nonAdminUser: IUser = {
name: 'Bob',
state: 'NY',
isAdmin: false,
address: '55 Main St',
deleted: false,
};
export const adminUser: IUser = {
name: 'Bob',
state: 'NY',
isAdmin: true,
address: '55 Main St',
deleted: false,
};
export const deletedAdminUser: IUser = {
name: 'Bob',
state: 'NY',
isAdmin: true,
address: '55 Main St',
deleted: true,
};
export const deletedNonAdmin: IUser = {
name: 'Bob',
state: 'NY',
isAdmin: false,
address: '55 Main St',
deleted: true,
};
// and on and on and on again...
.
:
? !
// src/factories/user
import faker from 'faker';
import {IUser} from 'src/pages/newYorkInfo';
export const makeFakeUser = (): IUser => {
return {
name: faker.name.firstName() + ' ' + faker.name.lastName(),
state: faker.address.stateAbbr(),
isAdmin: faker.random.boolean(),
address: faker.address.streetAddress(),
deleted: faker.random.boolean(),
}
}
makeFakeUser()
, .
, , , , . IUser, .
. , - . , .
import { makeFakeUser } from 'src/factories/user';
import {IUser} from 'src/pages/newYorkInfo';
// Arrange
const randomUser = makeFakeUser();
const deletedUser: IUser = { ...randomUser, ...{
deleted: true
};
cy.route('GET', '/user/**', deletedUser);
// Act
cy.visit('/dashboard');
// Assert
cy.find('ACCESS DENIED').should('exist')
, , . , , , API , "Access Denied"
.
, .
: mergePartially
spread
, . , , :
interface IUser {
userName: string;
preferences: {
lastUpdated?: Date;
favoriteColor?: string;
backupContact?: string;
mailingAddress: {
street: string;
city: string;
state: string;
zipCode: string;
}
}
}
, .
, , DRY. , , , "Main Street".
const userOnMainSt = makeFakeUser({
preferences: {
mailingAddress: {
street: 'Main Street'
}
}
});
, , , 7 . - . .
makeFakeUser
?
, mergePartially ( : mergePartially
).
const makeFakeUser = (override?: NestedPartial<IDeepObj>): IDeepObj => {
const seed: IDeepObj = {
userName: 'Bob Smith',
preferences: {
mailingAddress: {
street: faker.address.streetAddress(),
city: faker.address.city(),
state: faker.address.stateAbbr(),
zipCode: faker.address.zipCode(),
},
},
};
return mergePartially.deep(seed, override);
};
, , .
, .