Purpose: Comprehensive testing strategy and quality assurance procedures for Sprint 4 and Sprint 5.
Scope: Frontend, Backend, Integration, Performance, Security, and User Acceptance Testing
/\
/ \
/ E2E \ 10% - End-to-End Tests
/______\
/ \
/Integration\ 30% - Integration Tests
/____________\
/ \
/ Unit Tests \ 60% - Unit Tests
/__________________\
Current Status: 97.8% pass rate (390/399 tests passing)
cd backend
./mvnw test
./mvnw test -Dtest=ArticleServiceTest # Run specific test
./mvnw verify # Run all tests + integration tests
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
class ArticleServiceTest {
@Autowired
private ArticleService articleService;
@MockBean
private ArticleRepository articleRepository;
@Test
@DisplayName("Should create article with valid data")
void shouldCreateArticleWithValidData() {
// Given
Article article = Article.builder()
.title("Test Article")
.content("Content")
.category(ArticleCategory.CV_TIPS)
.build();
when(articleRepository.save(any(Article.class))).thenReturn(article);
// When
Article created = articleService.createArticle(article);
// Then
assertNotNull(created);
assertEquals("Test Article", created.getTitle());
assertNotNull(created.getSlug());
verify(articleRepository, times(1)).save(any(Article.class));
}
@Test
@DisplayName("Should throw exception when title is null")
void shouldThrowExceptionWhenTitleIsNull() {
// Given
Article article = Article.builder()
.content("Content")
.build();
// When & Then
assertThrows(ValidationException.class, () -> {
articleService.createArticle(article);
});
}
}
@WebMvcTest(ArticleController.class)
@Import(SecurityConfig.class)
class ArticleControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ArticleService articleService;
@Test
@DisplayName("GET /api/kb/articles should return paginated articles")
void shouldReturnPaginatedArticles() throws Exception {
// Given
Page<Article> articles = new PageImpl<>(List.of(
createTestArticle(1L, "Article 1"),
createTestArticle(2L, "Article 2")
));
when(articleService.getPublishedArticles(any(Pageable.class)))
.thenReturn(articles);
// When & Then
mockMvc.perform(get("/api/kb/articles")
.param("page", "0")
.param("size", "10"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray())
.andExpect(jsonPath("$.content.length()").value(2))
.andExpect(jsonPath("$.content[0].title").value("Article 1"));
}
@Test
@WithMockUser(roles = "ADMIN")
@DisplayName("POST /api/admin/kb/articles should require admin role")
void shouldRequireAdminRoleForCreate() throws Exception {
// Given
String articleJson = """
{
"title": "New Article",
"content": "Content",
"category": "CV_TIPS"
}
""";
// When & Then
mockMvc.perform(post("/api/admin/kb/articles")
.contentType(MediaType.APPLICATION_JSON)
.content(articleJson))
.andExpect(status().isCreated());
}
}
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ArticleRepositoryTest {
@Autowired
private ArticleRepository articleRepository;
@Test
@DisplayName("Should find article by slug")
void shouldFindArticleBySlug() {
// Given
Article article = createTestArticle("test-article");
articleRepository.save(article);
// When
Optional<Article> found = articleRepository.findBySlug("test-article");
// Then
assertTrue(found.isPresent());
assertEquals("test-article", found.get().getSlug());
}
@Test
@DisplayName("Should search articles by query")
void shouldSearchArticlesByQuery() {
// Given
articleRepository.saveAll(List.of(
createTestArticle("Java Developer Tips"),
createTestArticle("Spring Boot Guide"),
createTestArticle("React Tutorial")
));
// When
Page<Article> results = articleRepository.searchPublished(
"java",
PageRequest.of(0, 10)
);
// Then
assertEquals(1, results.getTotalElements());
assertEquals("Java Developer Tips", results.getContent().get(0).getTitle());
}
}
[ ] Article Entity Tests
[ ] Article Service Tests
[ ] Article Controller Tests
[ ] Admin Controller Tests
[ ] Repository Tests
Framework: Jasmine + Karma (default Angular setup)
cd frontend/recruitment-portal
npm test # Run all tests
npm test -- --include='**/kb-home.component.spec.ts' # Run specific test
npm run test:coverage # Generate coverage report
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { of, throwError } from 'rxjs';
import { KbHomeComponent } from './kb-home.component';
import { KbService } from '../../../services/kb.service';
import { ArticleCategory } from '../../../models/article-category.enum';
describe('KbHomeComponent', () => {
let component: KbHomeComponent;
let fixture: ComponentFixture<KbHomeComponent>;
let kbService: jasmine.SpyObj<KbService>;
beforeEach(async () => {
const kbServiceSpy = jasmine.createSpyObj('KbService', [
'getCategories',
'getArticles'
]);
await TestBed.configureTestingModule({
imports: [KbHomeComponent, HttpClientTestingModule],
providers: [
{ provide: KbService, useValue: kbServiceSpy }
]
}).compileComponents();
kbService = TestBed.inject(KbService) as jasmine.SpyObj<KbService>;
fixture = TestBed.createComponent(KbHomeComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should load categories on init', () => {
// Given
const mockCategories = [
{ category: ArticleCategory.CV_TIPS, count: 10 },
{ category: ArticleCategory.FAQ, count: 20 }
];
kbService.getCategories.and.returnValue(of(mockCategories));
// When
fixture.detectChanges(); // triggers ngOnInit
// Then
expect(component.categories.length).toBe(2);
expect(component.categories[0].count).toBe(10);
expect(kbService.getCategories).toHaveBeenCalled();
});
it('should handle error when loading categories fails', () => {
// Given
kbService.getCategories.and.returnValue(
throwError(() => new Error('API Error'))
);
// When
fixture.detectChanges();
// Then
expect(component.error).toBe('Kon categorieΓ«n niet laden.');
});
it('should navigate to search on search submit', () => {
// Given
component.searchQuery = 'java developer';
spyOn(component['router'], 'navigate');
// When
component.onSearch();
// Then
expect(component['router'].navigate).toHaveBeenCalledWith(
['/kennisbank/zoeken'],
{ queryParams: { q: 'java developer' } }
);
});
});
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { KbService } from './kb.service';
import { Article } from '../models/article.model';
import { ArticleCategory } from '../models/article-category.enum';
describe('KbService', () => {
let service: KbService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [KbService]
});
service = TestBed.inject(KbService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify(); // Ensure no outstanding HTTP requests
});
it('should fetch articles', () => {
// Given
const mockResponse = {
articles: [
{ id: 1, title: 'Article 1', slug: 'article-1' },
{ id: 2, title: 'Article 2', slug: 'article-2' }
],
totalElements: 2,
totalPages: 1,
currentPage: 0
};
// When
service.getArticles(0, 10).subscribe(result => {
// Then
expect(result.articles.length).toBe(2);
expect(result.totalElements).toBe(2);
});
const req = httpMock.expectOne(req =>
req.url.includes('/api/kb/articles') &&
req.params.get('page') === '0'
);
expect(req.request.method).toBe('GET');
req.flush(mockResponse);
});
it('should search articles with query parameter', () => {
// Given
const query = 'java';
// When
service.searchArticles(query, 0, 10).subscribe();
// Then
const req = httpMock.expectOne(req =>
req.url.includes('/api/kb/articles/search') &&
req.params.get('q') === query
);
expect(req.request.method).toBe('GET');
req.flush({ articles: [], totalElements: 0, totalPages: 0, currentPage: 0 });
});
it('should rate article', () => {
// Given
const articleId = 1;
const rating = 5;
// When
service.rateArticle(articleId, rating).subscribe();
// Then
const req = httpMock.expectOne(`${service['apiUrl']}/articles/${articleId}/rate`);
expect(req.request.method).toBe('POST');
expect(req.request.body).toEqual({ rating: 5 });
req.flush({ id: 1, articleId, rating, userId: 1, createdAt: new Date() });
});
});
[ ] CV Upload Button Component
[ ] CV Upload Modal Component
[ ] SCSS Optimization
[ ] KB Home Component
[ ] Article Detail Component
[ ] Article List Component
[ ] Search Results Component
[ ] Admin KB Editor Component
Tools: Postman or REST Assured (Java)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ArticleIntegrationTest {
@LocalServerPort
private int port;
@Autowired
private ArticleRepository articleRepository;
private String baseUrl;
@BeforeEach
void setUp() {
baseUrl = "http://localhost:" + port;
articleRepository.deleteAll();
}
@Test
@DisplayName("Should create, retrieve, and update article via API")
void shouldPerformFullArticleCRUD() {
// Create article
String createJson = """
{
"title": "Integration Test Article",
"summary": "Test summary",
"content": "Test content",
"category": "CV_TIPS",
"tags": ["test", "integration"]
}
""";
Article created = given()
.contentType(ContentType.JSON)
.body(createJson)
.when()
.post(baseUrl + "/api/admin/kb/articles")
.then()
.statusCode(201)
.extract()
.as(Article.class);
assertNotNull(created.getId());
assertNotNull(created.getSlug());
// Retrieve article
Article retrieved = given()
.when()
.get(baseUrl + "/api/kb/articles/" + created.getSlug())
.then()
.statusCode(200)
.extract()
.as(Article.class);
assertEquals(created.getId(), retrieved.getId());
assertEquals("Integration Test Article", retrieved.getTitle());
// Update article
String updateJson = """
{
"title": "Updated Integration Test Article",
"content": "Updated content"
}
""";
given()
.contentType(ContentType.JSON)
.body(updateJson)
.when()
.put(baseUrl + "/api/admin/kb/articles/" + created.getId())
.then()
.statusCode(200);
// Verify update
Article updated = given()
.when()
.get(baseUrl + "/api/kb/articles/" + created.getSlug())
.then()
.statusCode(200)
.extract()
.as(Article.class);
assertEquals("Updated Integration Test Article", updated.getTitle());
}
}
[ ] Authentication Flow
[ ] Application Submission Flow
[ ] Knowledge Base Flow
Tools: JMeter or Gatling
Scenario 1: Article List API
- 100 concurrent users
- Duration: 5 minutes
- Requests: GET /api/kb/articles?page=0&size=10
- Expected: <200ms average response time
Scenario 2: Article Search API
- 50 concurrent users
- Duration: 5 minutes
- Requests: GET /api/kb/articles/search?q=java
- Expected: <500ms average response time
Scenario 3: Article Detail API
- 200 concurrent users
- Duration: 5 minutes
- Requests: GET /api/kb/articles/{slug}
- Expected: <150ms average response time
Tools: Lighthouse, WebPageTest
[ ] Backend Performance
[ ] Frontend Performance
npm audit (frontend)./mvnw dependency-check:check (backend)# Backend dependency check
cd backend
./mvnw org.owasp:dependency-check-maven:check
# Frontend audit
cd frontend/recruitment-portal
npm audit
npm audit fix
Tools: axe DevTools, WAVE, Lighthouse Accessibility Audit
| Browser | Version | Desktop | Mobile | |---------|---------|---------|--------| | Chrome | Latest | β | β | | Firefox | Latest | β | β | | Safari | Latest | β | β | | Edge | Latest | β | N/A |
[ ] Chrome (Desktop)
[ ] Firefox (Desktop)
[ ] Safari (Desktop)
[ ] Edge (Desktop)
| Device Category | Viewport | Test Devices | |----------------|----------|--------------| | Small Mobile | 320px | iPhone SE | | Medium Mobile | 375px | iPhone 12 | | Large Mobile | 414px | iPhone 14 Pro Max | | Tablet | 768px | iPad | | Large Tablet | 1024px | iPad Pro |
Acceptance Criteria:
Acceptance Criteria:
Acceptance Criteria:
-- Create test database
CREATE DATABASE interimplaza_test;
-- Seed test data
INSERT INTO articles (title, slug, content, category, published) VALUES
('Test Article 1', 'test-article-1', '# Test Content', 'CV_TIPS', true),
('Test Article 2', 'test-article-2', '# Test Content', 'FAQ', true);
INSERT INTO users (email, password, role) VALUES
('test@example.com', '$2a$10$...', 'USER'),
('admin@interimplaza.nl', '$2a$10$...', 'ADMIN');
**Title:** [Component] Brief description
**Severity:** Critical | High | Medium | Low
**Environment:**
- Browser: Chrome 120
- OS: macOS 14.0
- Device: Desktop
**Steps to Reproduce:**
1. Navigate to /kennisbank
2. Click on "CV Tips" category
3. Observe error
**Expected Result:**
Should display list of CV tip articles
**Actual Result:**
Page shows 500 error
**Screenshots:**
[Attach screenshot]
**Console Errors:**
Error: Cannot read property 'articles' of undefined
**Additional Context:**
Only happens when user is not logged in
Last Updated: 2025-10-15 Status: Ready for Implementation Testing Framework: JUnit 5, Jasmine/Karma, Playwright (E2E)
Reacties