This guide provides comprehensive instructions for writing and running tests using Jasmine in this project.
- Basic knowledge of JavaScript
- Understanding of testing concepts
- Node.js installed (optional, for npm scripts)
- Browser Method: Open
SpecRunner.htmlin your web browser - Command Line: Run
npm test(if Node.js is set up)
describe('Component Name', function() {
it('should do something specific', function() {
// Arrange
const input = 'test input';
// Act
const result = functionUnderTest(input);
// Assert
expect(result).toEqual('expected output');
});
});- Group related tests together
- Use clear, descriptive names
- Can be nested for better organization
describe('Calculator', function() {
describe('addition', function() {
it('should add two positive numbers', function() {
// test implementation
});
it('should handle negative numbers', function() {
// test implementation
});
});
describe('division', function() {
it('should divide numbers correctly', function() {
// test implementation
});
it('should throw error when dividing by zero', function() {
// test implementation
});
});
});- Each
itblock should test one specific behavior - Write descriptive test names
- Follow the format: "should [expected behavior]"
expect(value).toEqual(expected); // Deep equality
expect(value).toBe(expected); // Same object reference
expect(value).not.toEqual(unexpected); // Negationexpect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();expect(string).toContain('substring');
expect(string).toMatch(/pattern/);
expect(array).toContain(item);
expect(array).toHaveLength(3);expect(function() {
riskyFunction();
}).toThrow();
expect(function() {
riskyFunction();
}).toThrowError('Specific error message');describe('MyClass', function() {
let instance;
beforeEach(function() {
instance = new MyClass();
});
afterEach(function() {
instance = null;
});
it('should initialize properly', function() {
expect(instance).toBeDefined();
});
});describe('Database Tests', function() {
beforeAll(function() {
// Expensive setup that runs once
setupDatabase();
});
afterAll(function() {
// Cleanup that runs once
teardownDatabase();
});
});describe('Event Handler', function() {
it('should call callback when event occurs', function() {
const callback = jasmine.createSpy('callback');
const handler = new EventHandler(callback);
handler.triggerEvent();
expect(callback).toHaveBeenCalled();
});
});describe('User Service', function() {
it('should log user activity', function() {
const user = new User();
spyOn(user, 'log');
user.performAction();
expect(user.log).toHaveBeenCalledWith('action performed');
});
});describe('API Client', function() {
it('should handle API response', function() {
const apiClient = new ApiClient();
spyOn(apiClient, 'makeRequest').and.returnValue(Promise.resolve({data: 'test'}));
const result = apiClient.getData();
expect(result).toBeDefined();
});
});describe('Async Function', function() {
it('should resolve with correct data', async function() {
const result = await asyncFunction();
expect(result).toEqual('expected data');
});
it('should reject with error', async function() {
try {
await failingAsyncFunction();
fail('Expected function to throw');
} catch (error) {
expect(error.message).toContain('expected error');
}
});
});describe('Callback Function', function() {
it('should call callback with result', function(done) {
callbackFunction(function(result) {
expect(result).toBeDefined();
done();
});
});
});beforeEach(function() {
jasmine.addMatchers({
toBeWithinRange: function() {
return {
compare: function(actual, min, max) {
return {
pass: actual >= min && actual <= max,
message: `Expected ${actual} to be between ${min} and ${max}`
};
}
};
}
});
});
it('should be within range', function() {
expect(5).toBeWithinRange(1, 10);
});- One test file per source file
- Group related tests in describe blocks
- Use clear, descriptive test names
- Keep tests independent
- Follow AAA pattern (Arrange, Act, Assert)
- Test one thing at a time
- Use meaningful assertions
- Test both positive and negative cases
- Aim for high coverage (>90%)
- Focus on critical code paths
- Don't sacrifice quality for coverage
- Keep tests fast
- Use beforeEach for common setup
- Mock external dependencies
- Avoid unnecessary test data
describe('Person', function() {
let person;
beforeEach(function() {
person = new Person('John', 'Doe');
});
describe('constructor', function() {
it('should set first name', function() {
expect(person.firstName).toBe('John');
});
it('should set last name', function() {
expect(person.lastName).toBe('Doe');
});
});
describe('getFullName', function() {
it('should return full name', function() {
expect(person.getFullName()).toBe('John Doe');
});
});
});describe('Calculator', function() {
describe('divide', function() {
it('should throw error when dividing by zero', function() {
const calculator = new Calculator();
expect(function() {
calculator.divide(10, 0);
}).toThrowError('Division by zero');
});
});
});describe('Button Component', function() {
it('should call onClick handler when clicked', function() {
const handler = jasmine.createSpy('clickHandler');
const button = new Button(handler);
button.click();
expect(handler).toHaveBeenCalled();
});
});- Async tests timing out: Use proper async patterns
- Spy not being called: Check method names and timing
- Unexpected values: Use console.log for debugging
- Test interference: Ensure proper cleanup
- Use fit() and fdescribe() to focus on specific tests
- Add console.log statements for debugging
- Check the browser console for errors
- Use browser dev tools for breakpoints