MiddleCodeRareNot answered yet
Design a testable DB-backed product service
Design a service that fetches products from a database by filter and prints them, and that is unit-testable without a real database.
Requirements:
- depend on the abstract
IProductRepository, not a concrete DB class (Dependency Injection) - provide a
FakeProductRepositoryso tests run in-memory, no DB connection - the interface needs a
virtualdestructor; return results by value, not raw pointers
struct Product { int id; std::string name; double price; std::string category; };
struct ProductFilter { std::optional<std::string> category; std::optional<double> maxPrice; };
class IProductRepository {
public:
// your code here
};
class FakeProductRepository : public IProductRepository {
// your code here
};
class ProductService {
public:
explicit ProductService(IProductRepository& repo);
void printByFilter(const ProductFilter& f) const;
// your code here
};
Write the implementation.
Define IProductRepository (pure virtual fetchByFilter), SqlProductRepository for real queries, FakeProductRepository for tests. ProductService depends only on the interface — Dependency Injection enables tests without a database.
- ✗Hardcoding the database call inside the service — impossible to unit test without a real DB
- ✗Not making the destructor of the repository interface virtual — deleting through base pointer is UB
- ✗Returning raw pointers from the repository — use
std::vector<Product>by value or smart pointers
- →How would you add pagination to
fetchByFilter? - →How does this design change if the repository must be thread-safe?
Contents
Task
Implement:
Productstruct (id, name, price, category)ProductFilterstruct (optional category, max price)IProductRepositoryinterface withfetchByFilterFakeProductRepositorystub for testsProductServicethat uses the repository and prints products
Solution
#include <iostream>
#include <string>
#include <vector>
#include <optional>
#include <memory>
#include <cassert>
struct Product {
int id;
std::string name;
double price;
std::string category;
};
struct ProductFilter {
std::optional<std::string> category;
std::optional<double> maxPrice;
};
class IProductRepository {
public:
virtual ~IProductRepository() = default;
virtual std::vector<Product> fetchByFilter(const ProductFilter& f) const = 0;
};
class FakeProductRepository : public IProductRepository {
public:
void add(Product p) { products_.push_back(std::move(p)); }
std::vector<Product> fetchByFilter(const ProductFilter& f) const override {
std::vector<Product> result;
for (const auto& p : products_) {
if (f.category && p.category != *f.category) continue;
if (f.maxPrice && p.price > *f.maxPrice) continue;
result.push_back(p);
}
return result;
}
private:
std::vector<Product> products_;
};
class ProductService {
public:
explicit ProductService(IProductRepository& repo) : repo_(repo) {}
void printByFilter(const ProductFilter& f) const {
auto products = repo_.fetchByFilter(f);
if (products.empty()) { std::cout << "(no products match)\n"; return; }
for (const auto& p : products)
std::cout << "[" << p.id << "] " << p.name
<< " | " << p.category << " | $" << p.price << '\n';
}
private:
IProductRepository& repo_;
};
int main() {
FakeProductRepository repo;
repo.add({1, "Hammer", 12.50, "tools"});
repo.add({2, "Keyboard", 89.00, "electronics"});
repo.add({3, "Screwdriver", 7.99, "tools"});
auto r = repo.fetchByFilter({.category = "tools"});
assert(r.size() == 2);
ProductService svc(repo);
std::cout << "--- tools ---\n";
svc.printByFilter({.category = "tools"});
return 0;
}
Key points
| Aspect | Decision |
|---|---|
| Dependency Injection | ProductService takes IProductRepository& → easy to swap in a fake |
| Testability | FakeProductRepository implements the interface in-memory |
| Virtual destructor | IProductRepository::~IProductRepository() = default |
| Return type | std::vector<Product> by value — safe and clear |
Contents