Kliensoldali webprogramozás
Horváth Győző
Egyetemi docens
1117 Budapest, Pázmány Péter sétány 1/c., 2.408-as szoba
Tel: (1) 372-2500/8469
horvath.gyozo@inf.elte.hu
function add(a, b) {
return a + b;
}
console.assert(add(3, 2) === 5, '3 + 2 should be equal 5');
console.assert(add(10, 0) === 10, '10 + 0 should be equal 10');
Jasmine
<!-- keretrendszer betöltése -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/jasmine.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/jasmine.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/jasmine-html.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/boot.min.js"></script>
<!-- az alkalmazás és teszt betöltése -->
<script src="app.js"></script>
<script src="app.test.js"></script>
// app.test.js
describe('factorial', () => {
it('0! should be 1', () => {
expect(factorial(0)).toBe(1);
})
})
<!-- keretrendszer betöltése -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/jasmine.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/jasmine.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/jasmine-html.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/boot.min.js"></script>
<input type="text" id="nev"> <br>
<input type="button" value="Köszönj!" id="gomb"> <br>
<span id="kimenet"></span>
<!-- az alkalmazás és teszt betöltése -->
<script src="hello.js"></script>
<script src="hello.test.js"></script>
describe('felület működése', () => {
// saját $ függvény
function $(sel) { return document.querySelector(sel); }
// minden teszt előtt készítsük elő a felületet
beforeEach(() => {
$('#nev').value = '';
$('#kimenet').innerHTML = '';
});
// tesztek
it(`Minden megjelenik`, () => {
expect($('input#nev')).not.toBeNull();
expect($('input#gomb')).not.toBeNull();
expect($('span#kimenet')).not.toBeNull();
});
it(`Üresen hagyva Hello ! jelenik meg`, () => {
$('#gomb').click(); // elvégez
expect($('#kimenet').textContent).toBe('Hello !'); // ellenőriz
});
it(`Szöveget írva be helyes üdvözlés lesz`, () => {
$('#nev').value = 'alma'; // előkészít
$('#gomb').dispatchEvent(new Event('click')); // elvégez
expect($('#kimenet').textContent).toBe('Hello alma!'); // ellenőriz
});
});
// ESLint: for-direction rule
for (var i = 0; i < 10; i--) {
console.log(i)
}
// TypeScript
const two = '2'
const result = add(1, two)
Testreszabható szabályokkal
// sinon.js fake timer example
{
setUp: function () {
this.clock = sinon.useFakeTimers();
},
tearDown: function () {
this.clock.restore();
},
"test should animate element over 500ms" : function(){
var el = jQuery("<div></div>");
el.appendTo(document.body);
el.animate({ height: "200px", width: "200px" });
this.clock.tick(510);
assertEquals("200px", el.css("height"));
assertEquals("200px", el.css("width"));
}
}
// math.js
export const add = (a, b) => a + b
export const multiply = (a, b) => a * b
// math.test.js
import { add, multiply } from "./math";
it("should add two positive numbers", () => {
// Arrange
const a = 10;
const b = 32;
// Act
const sum = add(a, b);
// Assert
expect(sum).toEqual(42);
});
it("should add two negative numbers", () => {
expect(add(-3, -4)).toBe(-7);
});
// math.test.js
import { multiply } from "./math";
describe('multiply', () => {
it("should multiply two negative numbers", () => {
expect(multiply(-3, -4)).toBe(12);
});
it("should multiply two positive numbers", () => {
expect(multiply(3, 6)).toBe(18);
});
it("should multiply a positive and a negative number", () => {
expect(multiply(-3, 4)).toBe(-12);
});
describe('subcatgeory', () => {
it('should test', () => {
expect(true).toBe(true);
})
})
});
beforeAll(() => { /* ... */ });
afterAll(() => { /* ... */ });
beforeEach(() => { /* ... */ });
afterEach(() => { /* ... */ });
describe('Nested block', () => {
beforeAll(() => { /* ... */ });
afterAll(() => { /* ... */ });
beforeEach(() => { /* ... */ });
afterEach(() => { /* ... */ });
});
expect(2 + 2).toBe(4);
expect(2 + 2).not.toBe(4);
expect({one: 1, two: 2}).toEqual({one: 1, two: 2});
// Numbers
const value = 2 + 2;
expect(value).toBe(4);
expect(value).toEqual(4);
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
// Strings
expect('Christoph').toMatch(/stop/);
expect('team').not.toMatch(/I/);
// Arrays
expect(shoppingList).toContain('beer');
// Errors
expect(compileAndroidCode).toThrow();
// users.js
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
// users.test.js
import axios from 'axios';
import Users from './users';
jest.mock('axios'); // automocking
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
export const SAVE_ITEM_REQUEST = 'SAVE_ITEM_REQUEST'
export const SAVE_ITEM_SUCCESS = 'SAVE_ITEM_SUCCESS'
export const saveItemRequest = () => ({
type: SAVE_ITEM_REQUEST
})
export const saveItemSuccess = (item) => ({
type: SAVE_ITEM_SUCCESS,
item
})
import { SAVE_ITEM_SUCCESS } from "./actions";
import { saveItemSuccess } from "./actions";
it('should create an action to save a new item in the store', () => {
const item = {
title: 'apple'
}
const expectedAction = {
type: SAVE_ITEM_SUCCESS,
item
}
expect(saveItemSuccess(item)).toEqual(expectedAction)
});
export const save = value => async dispatch => {
dispatch(saveItemRequest())
const item = await api.addItem({fruit: value})
dispatch(saveItemSuccess(item))
}
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { SAVE_ITEM_SUCCESS, SAVE_ITEM_REQUEST } from "./actions";
import { saveItemSuccess, save } from "./actions";
jest.mock("../api/storage-api", () => ({
api: {
async addItem(item) {
return item
}
}
}))
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
it('should create SAVE_ITEM_REQUEST and SAVE_ITEM_SUCCESS actions when saving an item', async () => {
const expectedActions = [
{ type: SAVE_ITEM_REQUEST },
{ type: SAVE_ITEM_SUCCESS, item: {fruit: 'apple'} }
]
const store = mockStore({})
await store.dispatch(save('apple'))
expect(store.getActions()).toEqual(expectedActions)
});
Tiszta függvény
it('should handle ADD_TODO', () => {
expect(
reducer([], {
type: types.ADD_TODO,
text: 'Run the tests'
})
).toEqual([
{
text: 'Run the tests',
completed: false,
id: 0
}
])
expect(
reducer(
[
{
text: 'Use Redux',
completed: false,
id: 0
}
],
{
type: types.ADD_TODO,
text: 'Run the tests'
}
)
).toEqual([
{
text: 'Run the tests',
completed: false,
id: 1
},
{
text: 'Use Redux',
completed: false,
id: 0
}
])
})
Nincs hiba a renderelés során
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
it('renders welcome message', () => {
const wrapper = shallow(<App />);
const welcome = <h2>Welcome to React</h2>;
expect(wrapper.contains(welcome)).toEqual(true);
});
import React from "react";
export default function Hello(props) {
if (props.name) {
return <h1>Hello, {props.name}!</h1>;
} else {
return <span>Hey, stranger</span>;
}
}
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import Hello from "./hello";
let container = null;
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("renders with or without a name", () => {
act(() => {
render(<Hello />, container);
});
expect(container.textContent).toBe("Hey, stranger");
act(() => {
render(<Hello name="Jenny" />, container);
});
expect(container.textContent).toBe("Hello, Jenny!");
act(() => {
render(<Hello name="Margaret" />, container);
});
expect(container.textContent).toBe("Hello, Margaret!");
});
export default function User(props) {
const [user, setUser] = useState(null);
async function fetchUserData(id) {
const response = await fetch("/" + id);
setUser(await response.json());
}
useEffect(() => {
fetchUserData(props.id);
}, [props.id]);
// ...
}
it("renders user data", async () => {
const fakeUser = {
name: "Joni Baez",
age: "32",
address: "123, Charming Avenue"
};
jest.spyOn(global, "fetch").mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve(fakeUser)
})
);
await act(async () => {
render(<User id="123" />, container);
});
expect(container.querySelector("summary").textContent).toBe(fakeUser.name);
expect(container.querySelector("strong").textContent).toBe(fakeUser.age);
expect(container.textContent).toContain(fakeUser.address);
global.fetch.mockRestore();
});
fetch-mock függvénykönyvtár
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../../actions/TodoActions'
import * as types from '../../constants/ActionTypes'
import fetchMock from 'fetch-mock'
import expect from 'expect' // You can use any testing library
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('async actions', () => {
afterEach(() => {
fetchMock.restore()
})
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => {
fetchMock.getOnce('/todos', {
body: { todos: ['do something'] },
headers: { 'content-type': 'application/json' }
})
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] })
return store.dispatch(actions.fetchTodos()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
})
jest.mock("./map", () => {
return function DummyMap(props) {
return (
<div data-testid="map">
{props.center.lat}:{props.center.long}
</div>
);
};
});
// ...
// Contact renders Map as a child
it("should render contact information", () => {
const center = { lat: 0, long: 0 };
act(() => {
render(
<Contact
name="Joni Baez"
email="test@example.com"
site="http://test.com"
center={center}
/>,
container
);
});
});
it("changes value when clicked", () => {
const onChange = jest.fn();
act(() => {
render(<Toggle onChange={onChange} />, container);
});
const button = document.querySelector("[data-testid=toggle]");
expect(button.innerHTML).toBe("Turn on");
act(() => {
button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(onChange).toHaveBeenCalledTimes(1);
expect(button.innerHTML).toBe("Turn off");
act(() => {
for (let i = 0; i < 5; i++) {
button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
}
});
expect(onChange).toHaveBeenCalledTimes(6);
expect(button.innerHTML).toBe("Turn on");
});
it("should render a greeting", () => {
act(() => {
render(<Hello />, container);
});
expect(
container.innerHTML
).toMatchSnapshot();
act(() => {
render(<Hello name="Jenny" />, container);
});
expect(
container.innerHTML
).toMatchSnapshot();
act(() => {
render(<Hello name="Margaret" />, container);
});
expect(
container.innerHTML
).toMatchSnapshot();
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render a greeting 1`] = `"<span>Hey, stranger</span>"`;
exports[`should render a greeting 2`] = `"<h1>Hello, Jenny!</h1>"`;
exports[`should render a greeting 3`] = `"<h1>Hello, Margaret!</h1>"`;
// __tests__/fetch.test.js
import React from 'react'
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import axiosMock from 'axios'
import Fetch from '../fetch'
jest.mock('axios')
test('loads and displays greeting', async () => {
const url = '/greeting'
render(<Fetch url={url} />)
axiosMock.get.mockResolvedValueOnce({
data: { greeting: 'hello there' },
})
fireEvent.click(screen.getByText('Load Greeting'))
await waitFor(() => screen.getByRole('heading'))
expect(axiosMock.get).toHaveBeenCalledTimes(1)
expect(axiosMock.get).toHaveBeenCalledWith(url)
expect(screen.getByRole('heading')).toHaveTextContent('hello there')
expect(screen.getByRole('button')).toHaveAttribute('disabled')
})
UI komponensek izolált megfigyelése
describe('My First Test', () => {
it('Gets, types and asserts', () => {
cy.visit('https://example.cypress.io')
cy.contains('type').click()
// Should be on a new URL which includes '/commands/actions'
cy.url().should('include', '/commands/actions')
// Get an input, type into it and verify that the value has been updated
cy.get('.action-email')
.type('fake@email.com')
.should('have.value', 'fake@email.com')
})
})