Endpoints REST, tests unitaires et intégration complète
Bootcode IWA-S04 — Semaine 21, Jour 4
1. Créer au moins 3 endpoints REST fonctionnels
GET, POST, et un endpoint d'action métier
2. Écrire au moins 4 tests unitaires qui passent
Avec repositories in-memory — pas de base de données
3. Faire fonctionner le flux complet
HTTP → Controller → Use Case → Repository
4. Préparer une démo fonctionnelle pour demain
Un scénario qui montre le flux de bout en bout
Rappel : le controller reçoit les use cases par injection
Et délègue — pas de logique métier dans le controller
Tips : l'ordre d'implémentation
Tests des use cases → controller → intégration
Format de présentation de demain
10 min par étudiant : 5 min démo + 5 min questions
Dernier sprint
Aider les étudiants à finir et préparer leur démo
Recevoir HTTP, déléguer aux use cases, retourner une réponse
« Le controller est la dernière couche. Il ne contient PAS de logique métier. »
Du HTTP au repository — chaque couche délègue à la suivante
HTTP Request → Controller → Use Case → Agrégat → Repository
DB / InMemory → Agrégat → Output → JSON Response → HTTP Response
Controller
Parse le body, appelle le use case, formate la réponse
Use Case
Orchestre : charge, appelle la méthode métier, sauvegarde
Agrégat
Valide l'invariant, produit l'événement
Repository
Persiste / charge via mapper + entity
Injecte les use cases via le container, délègue, formate la réponse
@injectable()
export class BookController {
constructor(
@inject('AddBook') private addBook: AddBook,
@inject('BorrowBook') private borrowBook: BorrowBook
) {}
borrow = async (req, res) => {
try {
const output = await this.borrowBook.execute(req.body);
res.status(200).json(output);
} catch (e) {
if (e instanceof DomainError) res.status(400).json({ error: e.message });
else res.status(500).json({ error: 'Internal error' });
}
}
}
❌ Logique métier dans le controller
borrow = async (req, res) => {
const book = await repo.findById(req.body.bookId);
if (book.isBorrowed) return res.status(400);
book.isBorrowed = true; // ❌ mutation directe
await repo.save(book);
}
L'invariant n'est pas protégé. Duplication de la logique. Impossible à tester.
✅ Controller qui délègue
borrow = async (req, res) => {
try {
const out = await this.borrowBook.execute(req.body);
res.json(out);
} catch (e) { /* ... */ }
}
Le use case + l'agrégat gèrent la logique. Le controller est mince et testable.
Avec un InMemoryRepository — pas de base de données, pas de mock complexe
describe('BorrowBook', () => {
it('emprunte un livre disponible', async () => {
const repo = new InMemoryBookRepository();
const book = Book.init(BookId.create(), ISBN.create('978...'));
await repo.save(book);
const usecase = new BorrowBook(repo);
const result = await usecase.execute({ bookId: book.id.value, memberId: 'm1' });
expect(result.loanId).toBeDefined();
expect(result.dueDate).toBeInstanceOf(Date);
});
}
Chaque DomainError doit avoir un test qui prouve qu'elle se déclenche
it('refuse d'emprunter un livre déjà emprunté', async () => {
const repo = new InMemoryBookRepository();
const book = Book.init(...);
book.borrow(MemberId.create('m1')); // déjà emprunté
await repo.save(book);
const usecase = new BorrowBook(repo);
await expect(usecase.execute({ bookId: book.id.value, memberId: 'm2' }))
.rejects.toThrow(BookAlreadyBorrowedError);
});
💡 Objectif : au moins 4 tests. Un test par use case + un test par cas d'erreur important.
Le controller est résolu depuis le container, puis branché sur Express
const container = new Container();
LibraryBuild.build(container); // bind repositories + use cases
container.bind('BookController').to(BookController);
const app = express();
app.use(express.json());
const controller = container.get('BookController');
// Routes
app.post('/books', controller.add);
app.post('/books/:id/borrow', controller.borrow);
app.post('/books/:id/return', controller.return);
app.get('/loans/overdue', controller.listOverdue);
Un scénario qui montre le flux de bout en bout — simple et qui marche
Scénario en 4-5 étapes
Ajouter un livre → emprunter → tenter un double emprunt (erreur) → rendre → lister les retards
Tester avec curl ou Postman
Préparez les commandes à l'avance — pas d'improvisation live
Montrer l'erreur métier
Le double emprunt doit renvoyer un 400 avec un message clair — ça prouve que l'invariant fonctionne
Mieux vaut simple qui marche
Un projet modeste qui démontre le flux > un projet ambitieux qui plante en démo
❌ Passer trop de temps sur le controller
Le controller est la couche la plus simple. Codez d'abord les tests, le controller vient après.
⚠️ Oublier de préparer la présentation
La démo de demain compte autant que le code. Préparez votre scénario et vos commandes curl.
❌ Paniquer si tout n'est pas fini
La démo peut montrer ce qui fonctionne. Mieux vaut 3 use cases qui marchent que 10 qui plantent.
⚠️ Mettre de la logique métier dans le controller
Le controller délègue. Si vous avez un if (book.isBorrowed) dans le controller, c'est une erreur.
✅ Les tests unitaires utilisent des repositories in-memory — pas de base de données.
✅ Le controller est la dernière couche — il ne contient PAS de logique métier.
✅ Préparez la démo : un scénario qui montre le flux de bout en bout.
✅ Mieux vaut un projet simple qui fonctionne qu'un projet ambitieux qui plante.
Demain : présentations & démos — célébration ! 🎉