🔥 Darmowa Roadmapa .NET — kompletny plan nauki do pierwszej pracy. Pobierz i dołącz do Listy VIP →
Z realnych rozmów kwalifikacyjnych

TOP 50 pytań rekrutacyjnych
Junior .NET Developer

Zebrane z realnych rozmów kwalifikacyjnych. Każde pytanie ma wzorcową odpowiedź — taką, której rekruter naprawdę szuka.

50
pytań
7
kategorii
3
poziomy trudności
zakres materiału

7 kategorii — pełne pokrycie rozmowy technicznej

🔤

C# Podstawy

Pytania Q01–Q10 · typy, class vs struct, nullable reference types, async/await, pattern matching.

🧩

OOP & SOLID

Pytania Q11–Q20 · cztery filary OOP, pięć zasad SOLID, interfejsy i abstrakcje.

🌐

ASP.NET Core & Web API

Pytania Q21–Q30 · middleware, routing, dependency injection, cykl życia żądania.

🗄️

EF Core & bazy danych

Pytania Q31–Q37 · problem N+1, migracje, tracking, optymalizacja zapytań.

🏛️

Wzorce & architektura

Pytania Q38–Q43 · Repository Pattern, Clean Architecture, CQRS i wzorce projektowe.

🧪

Testowanie

Pytania Q44–Q47 · testy jednostkowe, TDD, mockowanie i dobre praktyki testowe.

🔧

Git, DevOps & dobre praktyki

Pytania Q48–Q50 · kontrola wersji, CI/CD i standardy pracy w zespole.

przedsmak

Przykładowe pytania z listy

Kliknij, żeby zobaczyć, o co naprawdę pyta rekruter.

Q01 Jaka jest różnica między class a struct?
Klasa to typ referencyjny (żyje na stercie, przekazywana przez referencję), struktura to typ wartościowy (kopiowana przy przypisaniu). Rekruter sprawdza, czy rozumiesz konsekwencje: wydajność, semantykę kopiowania i to, kiedy struct faktycznie ma sens. Pełną wzorcową odpowiedź z kodem znajdziesz w materiale TOP 50.
Q07 Jak działa async/await w C#?
To nie „nowe wątki”, tylko maszyna stanów zwalniająca wątek na czas operacji I/O. Rekruter chce usłyszeć o Task, kontynuacjach i o tym, dlaczego async void to problem. W materiale: odpowiedź wzorcowa + typowe pułapki.
Q14 Wymień i omów cztery filary OOP
Enkapsulacja, dziedziczenie, polimorfizm, abstrakcja — ale sama wyliczanka nie wystarczy. Wygrywa kandydat, który każdy filar pokaże na krótkim przykładzie z kodu. Dokładnie tak są skonstruowane odpowiedzi w TOP 50.
Q18 Na czym polega pięć zasad SOLID?
SRP, OCP, LSP, ISP, DIP — rekruter pyta o nie niemal zawsze. Kluczem jest umiejętność wskazania, kiedy zasada jest łamana i jak to naprawić refaktoryzacją. Materiał zawiera przykłady „przed i po” dla każdej zasady.
Q24 Czym jest middleware w ASP.NET Core?
Pipeline żądania HTTP: każdy middleware może obsłużyć żądanie, przekazać je dalej lub przerwać przetwarzanie. Warto znać kolejność rejestracji i umieć napisać własny middleware — oba tematy są w wzorcowej odpowiedzi.
Q33 Czym jest problem N+1 w EF Core i jak go rozwiązać?
Jedno zapytanie o listę + N zapytań o relacje = zabójca wydajności. Odpowiedź wzorcowa omawia Include, projekcje do DTO i kiedy lazy loading robi więcej szkody niż pożytku.
Q40 Wyjaśnij Repository Pattern i Clean Architecture
Separacja logiki domenowej od dostępu do danych oraz warstwy Domain / Application / Infrastructure / API. Rekruter sprawdza, czy rozumiesz „dlaczego”, a nie tylko rysunek z internetu. W TOP 50: odpowiedź + typowe pytania doprecyzowujące.

To tylko 7 z 50 pytań. Pozostałe 43 — wraz z pełnymi wzorcowymi odpowiedziami i kodem — czekają poniżej.

Chcę pełną listę 50 pytań ↓
pełna baza

Pełna lista 50 pytań z odpowiedziami

C# C# Podstawy Q01–Q10
01 Jaka jest różnica między class a struct w C#? łatwe +

class to typ referencyjny — obiekt żyje na stercie (heap), zmienna przechowuje referencję. struct to typ wartościowy — dane leżą na stosie (stack) lub inline w obiekcie nadrzędnym, kopiowane przez wartość przy przypisaniu.

// class — kopiujemy referencję Point a = new Point(1, 2); // Point to class Point b = a; b.X = 99; Console.WriteLine(a.X); // 99 — ten sam obiekt na stercie// struct — kopiujemy wartość PointS c = new PointS(1, 2); // PointS to struct PointS d = c; d.X = 99; Console.WriteLine(c.X); // 1 — niezależna kopia
Kiedy struct? Małe, niemutowalne dane: Vector2, Color, DateTime. Unikaj struct większych niż ~16 bajtów lub mutowalnych — kopiowanie jest kosztowne i podatne na błędy.
02 Czym jest nullable reference type i jak działa w C# 8+? łatwe +

Od C# 8 typy referencyjne są domyślnie non-nullable. Kompilator ostrzega, gdy możliwy jest NullReferenceException. Dodanie ? jawnie oznacza, że zmienna może być null.

#nullable enable string name = null; // ⚠️ warning CS8600 string? name = null; // OK — świadomie nullablevoid Greet(string? name) { // null-forgiving operator — “kompilatorze, wiem co robię” Console.WriteLine(name!.Length);// lepiej — jawny null check if (name is not null) Console.WriteLine(name.Length); }
W projekcie: włącz <Nullable>enable</Nullable> w .csproj. Eliminuje całą klasę błędów runtime zanim trafią na produkcję.
03 Co to jest async/await i jak działa pod spodem? średnie +

async/await to lukier składniowy (syntactic sugar) na maszynę stanów generowaną przez kompilator. Przy pierwszym await, który nie jest gotowy, metoda zwraca kontrolę do wywołującego. Jeśli w danym momencie istnieje SynchronizationContext (np. WPF, WinForms, klasyczny ASP.NET), kontynuacja domyślnie do niego wraca — w przeciwnym razie (m.in. w ASP.NET Core i aplikacjach konsolowych) wraca po prostu na wątek z puli.

// Złe — blokuje wątek i grozi deadlockiem string json = httpClient.GetStringAsync(url).Result; // ❌ deadlock ryzyko!// Dobre string json = await httpClient.GetStringAsync(url);// Bez powrotu do kontekstu (np. w kodzie bibliotecznym) string json = await httpClient.GetStringAsync(url) .ConfigureAwait(false);
Pułapka: nigdy nie mieszaj .Result / .Wait() z async — grozi deadlockiem tam, gdzie istnieje SynchronizationContext (klasyczny ASP.NET, WPF, WinForms). Jeśli async, to async przez cały callstack.
Częsta pułapka na rozmowie: ASP.NET Core (od .NET Core 1.0) nie ma SynchronizationContext — więc klasyczny deadlock z .Result opisany w podręcznikach do ASP.NET (System.Web) tu nie wystąpi z tego samego powodu. ConfigureAwait(false) wciąż jest dobrą praktyką w kodzie bibliotecznym (mniejszy narzut, przenośność), ale w ASP.NET Core nie chroni przed deadlockiem — chroni przed niepotrzebnym marshalingiem kontekstu.
04 Różnica między IEnumerable<T>, ICollection<T> a IList<T>? łatwe +

IEnumerable<T> — tylko do odczytu, leniwa iteracja, brak Count. ICollection<T> dodaje Count, Add, Remove. IList<T> dodaje dostęp indeksowany oraz Insert/RemoveAt.

// Zasada: używaj najwęższego interfejsu wystarczającego do celu void Process(IEnumerable<Order> orders) { /* tylko iteracja */ } void Fill(ICollection<Order> orders) { orders.Add(new Order()); } void Swap(IList<Order> orders, int i, int j) { (orders[i], orders[j]) = (orders[j], orders[i]); }
Wzorzec: parametry metod deklaruj jako IEnumerable<T> (najwęższy wystarczający kontrakt — nie wymuszasz konkretnej kolekcji na wywołującym), pola prywatne jako List<T> (konkretna implementacja).
05 Co to jest LINQ i jaka jest różnica między Select, Where a FirstOrDefault? łatwe +

LINQ to deklaratywny mechanizm zapytań do dowolnych źródeł danych (kolekcje, bazy danych, XML). Operatory są leniwe — wykonują się dopiero przy pierwszej enumeracji.

var orders = new List<Order> { … };// Where — filtruje, zwraca IEnumerable<T> var active = orders.Where(o => o.IsActive);// Select — projekcja/transformacja każdego elementu var ids = orders.Select(o => o.Id);// FirstOrDefault — zwraca pierwszy pasujący lub null (nie rzuca wyjątku) var order = orders.FirstOrDefault(o => o.Id == 5); // vs First() — rzuca InvalidOperationException gdy brak elementu
Na rozmowie: pokaż świadomość deferred execution — LINQ query nie wykonuje się przy deklaracji, tylko przy enumeracji: foreach, ToList(), Count().
06 Czym jest record w C# 9+ i kiedy go używać? średnie +

record to typ referencyjny z wbudowaną semantyką wartości: auto-generowane Equals, GetHashCode i ToString oparte na właściwościach, nie referencji. Kompilator generuje też wyrażenie with do tworzenia niemutowalnych kopii.

record Person(string FirstName, string LastName);var p1 = new Person(“Jan”, “Kowalski”); var p2 = new Person(“Jan”, “Kowalski”);Console.WriteLine(p1 == p2); // true — porównanie wg wartości właściwościvar p3 = p1 with { LastName = “Nowak” }; // niemutowalna kopia z nowym nazwiskiem// record struct (C# 10) — typ wartościowy + semantyka wartości record struct Point(double X, double Y);
Kiedy record? DTO, Value Objects w DDD, odpowiedzi z API, dane konfiguracyjne — wszędzie tam, gdzie zależy na niemutowalności i porównaniu wg treści.
07 Co to jest delegate, Action, Func i Predicate? średnie +

delegate to typowany wskaźnik na metodę. Action<T>, Func<T,TResult>, Predicate<T> to gotowe, wbudowane delegaty — nie trzeba deklarować własnych.

// Func — zwraca wartość; ostatni parametr generyczny to typ zwracany Func<int, int, int> add = (a, b) => a + b; Console.WriteLine(add(2, 3)); // 5// Action — nie zwraca wartości (void) Action<string> log = msg => Console.WriteLine(msg);// Predicate — przyjmuje T, zwraca bool Predicate<int> isEven = n => n % 2 == 0;// Closure — lambda “zamyka” zmienną z zewnętrznego scope int threshold = 10; Func<int, bool> above = n => n > threshold; threshold = 20; // uwaga: zmiana wpływa na zachowanie closure!
08 Jak działa Garbage Collector w .NET i co to są generacje? średnie +

GC w .NET jest generacyjny — obiekty podzielone na Gen0, Gen1, Gen2 oraz Large Object Heap (LOH, ≥85 KB). Gen0 jest zbierana najczęściej i najszybciej (krótko żyjące obiekty). Obiekty, które przeżyją kolekcję, trafiają do Gen1, a następnie do Gen2.

// Finalizer bez IDisposable to antypattern — opóźnia zwolnienie o co najmniej jeden cykl GCclass Resource : IDisposable { private bool _disposed;public void Dispose() { Dispose(true); GC.SuppressFinalize(this); // bez tego finalizer odpali się ponownie }protected virtual void Dispose(bool disposing) { /* cleanup */ } ~Resource() => Dispose(false); // finalizer — ostatnia deska ratunku }
Praktyka: używaj using / await using dla IDisposable. Unikaj wymuszania kolekcji (GC.Collect()) — prawie zawsze błąd projektowy.
09 Czym różni się string od StringBuilder? łatwe +

string jest niemutowalny — każda konkatenacja tworzy nowy obiekt na stercie. W pętli o n iteracjach to n alokacji (O(n)), ale każda kolejna kopiuje cały dotychczasowy string — więc łączny koszt kopiowania danych rośnie do O(n²). StringBuilder mutuje wewnętrzny bufor (z realokacją co jakiś czas) — O(n) alokacji i O(n) łącznego kosztu kopiowania.

// Złe — n konkatenacji = n nowych obiektów string, // ale koszt kopiowania rośnie kwadratowo (1+2+3+…+n) string result = “”; foreach (var item in items) result += item + “, “; // 100 iteracji ≈ 100 alokacji, ale O(n²) skopiowanych znaków// Dobre var sb = new StringBuilder(); foreach (var item in items) sb.Append(item).Append(“, “); string result = sb.ToString();// Dla 2–3 konkatenacji += jest całkowicie w porządku // C# 10+: rozważ string.Create() lub Span<char> dla hot paths
10 Co to jest pattern matching w C# i jakie są jego formy? średnie +

Pattern matching pozwala testować kształt i wartości danych w wyrażeniach is, switch i switch expression. C# 9/10 dodał wzorce relacyjne, logiczne i wzorce właściwości.

object obj = new Order { Status = “Active”, Amount = 150 };// Type pattern + property pattern if (obj is Order { Status: “Active”, Amount: > 100 } order) Console.WriteLine($”Duże zamówienie: {order.Amount}”);// Switch expression (C# 8+) string label = order.Amount switch { < 50 => “małe”, >= 50 and < 200 => “średnie”, >= 200 => “duże”, _ => “nieznane” };
OOP OOP & SOLID Q11–Q20
11 Wyjaśnij cztery filary OOP na konkretnych przykładach. łatwe +

Enkapsulacja — ukrywanie stanu, dostęp przez publiczne API. Dziedziczenie — współdzielenie kodu przez hierarchię klas. Polimorfizm — ten sam interfejs, różne zachowanie. Abstrakcja — modelowanie esencji domeny, ukrywanie szczegółów implementacji.

abstract class Shape { public abstract double Area(); }class Circle(double radius) : Shape { public override double Area() => Math.PI * radius * radius; }class Rectangle(double w, double h) : Shape { public override double Area() => w * h; }// Polimorficzne wywołanie — konkretny typ nie ma znaczenia List<Shape> shapes = [new Circle(5), new Rectangle(3, 4)]; double total = shapes.Sum(s => s.Area()); // 98.54…
12 Co to jest zasada SRP (Single Responsibility Principle)? łatwe +

Klasa powinna mieć jeden powód do zmiany — jeden obszar odpowiedzialności. “Jeden powód” to jeden aktor biznesowy, który może zażądać zmiany logiki.

// Naruszenie SRP — OrderService robi za dużo class OrderService { public void PlaceOrder(Order order) { /* logika zamówień */ } public void SendEmail(Order order) { /* wysyłka maila */ } // ← osobna odpowiedzialność public void SaveToCsv(Order order) { /* eksport danych */ } // ← osobna odpowiedzialność }// Po SRP — każda klasa ma jeden powód do zmiany class OrderService { public void PlaceOrder(Order order) { … } } class EmailService { public void SendConfirmation(Order order) { … } } class OrderExporter { public void ExportToCsv(IEnumerable<Order> orders) { … } }
Praktyczna wskazówka: jeśli opisujesz klasę słowem “i” (np. “zapisuje dane I wysyła maila”), SRP jest naruszone.
13 Czym jest Open/Closed Principle i jak go stosować w praktyce? średnie +

Kod powinien być otwarty na rozszerzenie, zamknięty na modyfikację. Nowe zachowania dodajemy przez nowe klasy/implementacje, nie przez zmianę istniejącego kodu.

// Naruszenie — każdy nowy typ rabatu wymaga modyfikacji metody decimal Calculate(Order o, string discountType) { if (discountType == “vip”) return o.Total * 0.9m; if (discountType == “promo”) return o.Total * 0.85m; // kolejne if’y przy każdej nowej promocji… return o.Total; }// Po OCP — nowy rabat = nowa klasa, stary kod niezmieniony interface IDiscountPolicy { decimal Apply(decimal total); }class VipDiscount : IDiscountPolicy { public decimal Apply(decimal t) => t * 0.9m; } class PromoDiscount : IDiscountPolicy { public decimal Apply(decimal t) => t * 0.85m; }class OrderService(IDiscountPolicy discount) { public decimal Calculate(Order o) => discount.Apply(o.Total); }
14 Co to jest Dependency Inversion Principle i czym różni się od Dependency Injection? średnie +

DIP (zasada) — moduły wysokiego poziomu nie zależą od modułów niskiego poziomu; obie strony zależą od abstrakcji. DI (technika) — mechanizm dostarczania zależności z zewnątrz. DI jest sposobem realizacji DIP.

// Naruszenie DIP — OrderService tworzy konkretną zależność class OrderService { private readonly SqlOrderRepository _repo = new SqlOrderRepository(); // tight coupling! }// DIP + DI — zależność od interfejsu, wstrzykiwana z zewnątrz class OrderService(IOrderRepository repo) // constructor injection { public async Task<Order?> GetOrder(int id) => await repo.GetByIdAsync(id); }// Rejestracja w kontenerze IoC (ASP.NET Core) builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
Na rozmowie: DIP to zasada projektowa (SOLID), DI to wzorzec implementacyjny. Kontener IoC to narzędzie automatyzujące DI.
15 Jaka jest różnica między abstract class a interface? Kiedy użyć którego? łatwe +

Klasa abstrakcyjna może mieć stan, konstruktory i konkretne metody — wyraża relację “jest”. Interfejs definiuje kontrakt zachowania — wyraża relację “potrafi”. Klasa może implementować wiele interfejsów, ale dziedziczyć tylko z jednej klasy.

// Abstract class — wspólna baza + współdzielone zachowanie abstract class Animal { public string Name { get; } protected Animal(string name) { Name = name; } public abstract void Speak(); // wymaga nadpisania public void Breathe() { /* wspólna */ } // dziedziczona }// Interface — zdolność/kontrakt (C# 8+ pozwala na domyślne implementacje) interface ISerializable { string Serialize(); }// Pies “jest” zwierzęciem ORAZ “potrafi” być serializowany class Dog : Animal, ISerializable { public Dog(string name) : base(name) { } public override void Speak() => Console.WriteLine(“Woof!”); public string Serialize() => JsonSerializer.Serialize(this); }
16 Co to jest LSP (Liskov Substitution Principle) i jak sprawdzić, czy jest naruszone? średnie +

Obiekt podklasy powinien być w stanie zastąpić obiekt klasy bazowej bez zmiany poprawności programu. Naruszenie objawia się rzucaniem wyjątków w nadpisanych metodach lub zawężaniem kontraktu.

// Klasyczne naruszenie LSP — Rectangle/Square class Rectangle { public virtual int Width { get; set; } public virtual int Height { get; set; } public int Area => Width * Height; }class Square : Rectangle { public override int Width { set { base.Width = base.Height = value; } } public override int Height { set { base.Width = base.Height = value; } } }// Test ujawniający naruszenie void TestArea(Rectangle r) { r.Width = 4; r.Height = 5; Debug.Assert(r.Area == 20); // ✅ dla Rectangle, ❌ dla Square (zwraca 25) }// Rozwiązanie: nie dziedzicz Square z Rectangle — mają różne kontrakty
Test LSP: czy mogę zastąpić klasę bazową podklasą bez modyfikowania testów jednostkowych napisanych dla klasy bazowej?
17 Co to jest ISP (Interface Segregation Principle)? łatwe +

Klient nie powinien być zmuszony do zależności od metod, których nie używa. Duże interfejsy rozbijamy na mniejsze, wyspecjalizowane.

// Naruszenie ISP — “fat interface” interface IWorker { void Work(); void Eat(); void Sleep(); }// Robot implementuje interfejs, ale nie je ani nie śpi class Robot : IWorker { public void Work() { /* OK */ } public void Eat() => throw new NotImplementedException(); // code smell! public void Sleep()=> throw new NotImplementedException(); }// Po ISP — segregacja interfejsów interface IWorkable { void Work(); } interface IEatable { void Eat(); } interface ISleepable { void Sleep(); }class Human : IWorkable, IEatable, ISleepable { /* implementuje wszystkie */ } class Robot : IWorkable { /* implementuje tylko Work */ }
18 Czym jest kompozycja vs dziedziczenie? Kiedy preferować jedno nad drugim? średnie +

Dziedziczenie (is-a) tworzy silne powiązanie hierarchiczne. Kompozycja (has-a) składa obiekt z mniejszych, wymiennych części. Zasada z GoF: “favor composition over inheritance”.

// Dziedziczenie — OK gdy relacja “jest” jest prawdziwa i stabilna class ElectricCar : Car { /* ElectricCar “jest” Car */ }// Kompozycja — elastyczność, łatwość testowania i podmiany zależności class OrderProcessor { private readonly IPaymentGateway _payment; private readonly IInventoryService _inventory; private readonly IEmailService _email;public OrderProcessor(IPaymentGateway p, IInventoryService i, IEmailService e) => (_payment, _inventory, _email) = (p, i, e); }
Czerwona flaga: głęboka hierarchia dziedziczenia (3+ poziomy) prawie zawsze sygnalizuje problem projektowy. Spłaszcz hierarchię i zastosuj kompozycję.
19 Co to jest klasa sealed i kiedy jej używać? łatwe +

sealed blokuje dziedziczenie z klasy (lub nadpisywanie metody wirtualnej). Daje kompilatorowi i JIT więcej możliwości optymalizacji (devirtualization), dokumentuje intencję projektową i chroni przed nieoczekiwanym rozszerzaniem.

sealed class SqlOrderRepository : IOrderRepository { // Nikt nie może dziedziczyć z tej klasy. // JIT wywołuje metody bezpośrednio (devirtualization = szybciej). }// sealed override — blokuje dalsze nadpisywanie class Base { public virtual void Do() { } } class Derived : Base { public sealed override void Do() { } } // class Further : Derived { override void Do() } // ← błąd kompilacji// Wzorzec: wyjątki domenowe często sealed sealed class OrderNotFoundException(int orderId) : Exception($”Order {orderId} not found.”);
20 Czym jest override vs new w kontekście metod? średnie +

override — nadpisuje wirtualną metodę klasy bazowej. Wywołanie przez referencję bazową uruchamia wersję z klasy pochodnej (polimorfizm runtime). new — ukrywa metodę bazową. Wywołanie przez referencję bazową uruchamia wersję bazową.

class Animal { public virtual void Speak() => Console.Write(“…”); } class DogA : Animal { public override void Speak() => Console.Write(“Woof!”); } class DogB : Animal { public new void Speak() => Console.Write(“Woof!”); }Animal a1 = new DogA(); Animal a2 = new DogB();a1.Speak(); // “Woof!” — override: polimorfizm działa a2.Speak(); // “…” — new: metoda bazowa nie jest nadpisana, tylko ukrytaDogB d = new DogB(); d.Speak(); // “Woof!” — przez konkretny typ działa wersja z DogB
Pułapka: new zamiast override łamie polimorfizm. Używaj new tylko świadomie, gdy celowo chcesz ukryć metodę bez polimorficznego zachowania.
API ASP.NET Core & Web API Q21–Q30
21 Jaki jest cykl życia żądania HTTP w ASP.NET Core? Co to jest middleware? średnie +

Żądanie przechodzi przez pipeline middleware — łańcuch komponentów, z których każdy może przetworzyć żądanie i/lub odpowiedź albo przekazać dalej przez wywołanie next().

// Kolejność middleware ma krytyczne znaczenie! app.UseExceptionHandler(“/error”); // musi być pierwszy app.UseHttpsRedirection(); app.UseAuthentication(); // kim jesteś? app.UseAuthorization(); // czy masz dostęp? app.UseRateLimiter(); app.MapControllers(); // routing + akcje// Własny middleware public class TimingMiddleware(RequestDelegate next) { public async Task InvokeAsync(HttpContext ctx) { var sw = Stopwatch.StartNew(); await next(ctx); sw.Stop(); ctx.Response.Headers[“X-Elapsed-Ms”] = sw.ElapsedMilliseconds.ToString(); } }
22 Różnica między Transient, Scoped i Singleton w DI? łatwe +

Transient — nowa instancja przy każdym pobraniu z kontenera. Scoped — jedna instancja na żądanie HTTP. Singleton — jedna instancja przez całe życie aplikacji.

builder.Services.AddTransient<IEmailSender, SmtpEmailSender>(); // bezstanowe builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>(); // DbContext, UoW builder.Services.AddSingleton<IConfiguration>(config); // cache, konfiguracja// PUŁAPKA: Captive Dependency // Singleton NIE powinien bezpośrednio zależeć od Scoped/Transient. class BadSingleton(IScopedService scoped) { } // ❌ Scoped “uwięziony” w Singleton
Captive Dependency — najczęstszy błąd DI na rozmowach. Singleton wstrzykuje Scoped → Scoped żyje tak długo jak Singleton. DbContext w Singleton to klasyczny przykład wycieku.
Uwaga na środowisko: kontener rzuci InvalidOperationException przy starcie tylko gdy ValidateScopes jest włączone — a to jest domyślnie ustawione tylko w środowisku Development (WebApplicationBuilder włącza ValidateScopes i ValidateOnBuild automatycznie w Development). Na produkcji, jeśli nie ustawisz tego jawnie, wyjątku nie będzie — Scoped po prostu zacznie po cichu wyciekać, co jest dużo groźniejsze niż szybki fail-fast. Jawne wymuszenie walidacji też na produkcji: builder.Host.UseDefaultServiceProvider(o => { o.ValidateScopes = true; o.ValidateOnBuild = true; });
23 Jak zwracać właściwe kody HTTP z kontrolera Web API? łatwe +

Używamy IActionResult / ActionResult<T> i pomocniczych metod z ControllerBase.

[ApiController] [Route(“api/[controller]”)] public class OrdersController(IOrderService service) : ControllerBase { [HttpGet(“{id}”)] public async Task<ActionResult<OrderDto>> Get(int id) { var order = await service.GetByIdAsync(id); if (order is null) return NotFound(new { message = $”Order {id} not found.” }); // 404 return Ok(order); // 200 + JSON }[HttpPost] public async Task<ActionResult<OrderDto>> Create(CreateOrderRequest req) { var order = await service.CreateAsync(req); return CreatedAtAction(nameof(Get), new { id = order.Id }, order); // 201 }[HttpDelete(“{id}”)] public async Task<IActionResult> Delete(int id) { await service.DeleteAsync(id); return NoContent(); // 204 } }
24 Co to jest walidacja modelu w ASP.NET Core i jak ją rozszerzyć? łatwe +

[ApiController] automatycznie zwraca 400 z detalami, gdy ModelState.IsValid == false. Data Annotations to proste atrybuty wbudowane w .NET — dla złożonej walidacji używamy FluentValidation.

// Data Annotations — proste, wbudowane public class CreateOrderRequest { [Required] [StringLength(100, MinimumLength = 3)] public string ProductName { get; set; } = “”;[Range(1, 10000)] public decimal Amount { get; set; } }// FluentValidation — kompleksowa, testowalna walidacja public class CreateOrderValidator : AbstractValidator<CreateOrderRequest> { public CreateOrderValidator() { RuleFor(x => x.ProductName).NotEmpty().Length(3, 100); RuleFor(x => x.Amount).GreaterThan(0).LessThanOrEqualTo(10000); } }builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderValidator>();
25 Jak działa autentykacja JWT w ASP.NET Core? średnie +

JWT (JSON Web Token) składa się z trzech części: header.payload.signature. Serwer generuje token po zalogowaniu użytkownika; klient wysyła go w nagłówku Authorization: Bearer <token>. ASP.NET Core weryfikuje podpis i claims automatycznie.

// Program.cs builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(opt => { opt.TokenValidationParameters = new() { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = config[“Jwt:Issuer”], ValidAudience = config[“Jwt:Audience”], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(config[“Jwt:Key”]!)) }; });// Generowanie tokenu (np. w LoginHandler) var credentials = new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config[“Jwt:Key”]!)), SecurityAlgorithms.HmacSha256);var token = new JwtSecurityToken( issuer: config[“Jwt:Issuer”], audience: config[“Jwt:Audience”], claims: [new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())], expires: DateTime.UtcNow.AddHours(1), signingCredentials: credentials);string tokenString = new JwtSecurityTokenHandler().WriteToken(token);
26 Co to jest IHostedService i BackgroundService? średnie +

IHostedService to interfejs dla usług startujących i zatrzymujących się razem z aplikacją. BackgroundService to klasa bazowa upraszczająca implementację długo działających workerów.

public class OrderCleanupWorker( IServiceScopeFactory scopeFactory, ILogger<OrderCleanupWorker> logger) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using var scope = scopeFactory.CreateScope(); var repo = scope.ServiceProvider.GetRequiredService<IOrderRepository>(); await repo.DeleteExpiredAsync(); logger.LogInformation(“Cleanup at {time}”, DateTimeOffset.UtcNow); await Task.Delay(TimeSpan.FromHours(1), stoppingToken); } } }builder.Services.AddHostedService<OrderCleanupWorker>();
Wzorzec: hosted services są rejestrowane jako Singleton — zależności Scoped (jak DbContext) wstrzykuj przez IServiceScopeFactory, nigdy bezpośrednio.
27 Co to jest Options Pattern i dlaczego jest lepszy od IConfiguration? średnie +

Options Pattern silnie typuje konfigurację, umożliwia walidację przy starcie i ułatwia testowanie — nie przekazujemy surowego IConfiguration w głąb kodu.

// appsettings.json: { “Email”: { “Host”: “smtp.gmail.com”, “Port”: 587 } }public class EmailOptions { public const string Section = “Email”; [Required] public string Host { get; set; } = “”; [Range(1, 65535)] public int Port { get; set; } }builder.Services .AddOptions<EmailOptions>() .BindConfiguration(EmailOptions.Section) .ValidateDataAnnotations() .ValidateOnStart(); // błędna konfiguracja = crash przy starcie, nie przy pierwszym użyciuclass EmailService(IOptions<EmailOptions> opts) { // IOptions — stała wartość, nie reaguje na zmiany pliku // IOptionsSnapshot — reload per-request (Scoped) // IOptionsMonitor — reload on-the-fly (Singleton) }
28 Jak działa Problem Details i jak ujednolicić obsługę błędów w API? średnie +

RFC 9457 (Problem Details) to standard odpowiedzi błędów HTTP API. ASP.NET Core 8 wprowadził wbudowany globalny exception handler, zastępując ręczne middleware z try/catch.

// Program.cs (ASP.NET Core 8+) builder.Services.AddProblemDetails(); app.UseExceptionHandler(); // globalny handlerapp.UseExceptionHandler(x => x.Run(async ctx => { var ex = ctx.Features.Get<IExceptionHandlerFeature>()?.Error; var pd = ex switch { NotFoundException e => new ProblemDetails { Status = 404, Title = e.Message }, ValidationException e => new ProblemDetails { Status = 422, Title = “Validation failed” }, _ => new ProblemDetails { Status = 500, Title = “Internal server error” } }; ctx.Response.StatusCode = pd.Status ?? 500; await ctx.Response.WriteAsJsonAsync(pd); }));// Odpowiedź zgodna z RFC 9457: // { “type”: “…”, “title”: “…”, “status”: 404, “detail”: “…” }
29 Co to są Minimal APIs i kiedy użyć ich zamiast kontrolerów? łatwe +

Minimal APIs (ASP.NET Core 6+) rejestrują endpointy bezpośrednio w Program.cs bez kontrolerów. Mniej boilerplate, lepsza wydajność, świetne dla mikrousług i małych API.

app.MapGet(“/api/orders/{id}”, async (int id, IOrderService svc) => { var order = await svc.GetByIdAsync(id); return order is null ? Results.NotFound() : Results.Ok(order); }) .WithName(“GetOrder”).WithOpenApi().RequireAuthorization();// Grupowanie endpointów (ASP.NET Core 7+) var orders = app.MapGroup(“/api/orders”).RequireAuthorization(); orders.MapGet(“/”, (IOrderService s) => s.GetAllAsync()); orders.MapPost(“/”, async (CreateOrderRequest req, IOrderService s) => { … }); orders.MapDelete(“/{id}”, async (int id, IOrderService s) => { … });
Kiedy kontroler? Duże API z wieloma akcjami i filtrami. Kiedy Minimal API? Proste CRUD, mikrousługa, prototyp — mniej plików, szybszy start.
30 Co to jest CORS i jak go skonfigurować w ASP.NET Core? łatwe +

CORS (Cross-Origin Resource Sharing) — przeglądarka blokuje żądania JavaScript do innej domeny. Serwer musi jawnie zezwolić przez nagłówki odpowiedzi.

builder.Services.AddCors(opt => { opt.AddPolicy(“Frontend”, policy => policy.WithOrigins(“https://app.dev-hobby.pl”, “http://localhost:4200”) .AllowAnyMethod().AllowAnyHeader() .AllowCredentials()); // wymagane przy cookies/Authorization headeropt.AddPolicy(“Public”, policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); });// Middleware — MUSI być przed MapControllers app.UseCors(“Frontend”); app.MapGet(“/public”, handler).RequireCors(“Public”);
Pułapka: AllowAnyOrigin() + AllowCredentials() = błąd runtime. Przeglądarki nie wysyłają danych uwierzytelniających do wildcard origin.
DB Entity Framework Core & Bazy Danych Q31–Q37
31 Co to jest problem N+1 w EF Core i jak go unikać? średnie +

Problem N+1 — zamiast jednego zapytania z JOIN, wykonujemy 1 zapytanie po listę + N dodatkowych zapytań dla każdego elementu. Demoluje wydajność przy większych danych.

// Problem — N+1: 1 query po Orders + 1 query PER zamówienie var orders = await ctx.Orders.ToListAsync(); foreach (var order in orders) Console.WriteLine(order.Customer.Name); // lazy load → dodatkowy SELECT!// Fix 1 — Eager Loading (Include) var orders = await ctx.Orders .Include(o => o.Customer).Include(o => o.Items) .ToListAsync(); // JEDEN SQL z JOIN// Fix 2 — Select (projekcja) — pobierz tylko to, co potrzebne var dtos = await ctx.Orders .Select(o => new OrderDto(o.Id, o.Customer.Name, o.Items.Count)) .ToListAsync();// Fix 3 — Split query (dla złożonych Include’ów) var orders = await ctx.Orders .Include(o => o.Items).ThenInclude(i => i.Product) .AsSplitQuery() // osobne SQL zamiast kartezjańskiego JOIN .ToListAsync();
Jak wykryć? Włącz logowanie SQL: optionsBuilder.LogTo(Console.WriteLine). Zobaczysz każdy SELECT.
32 Czym są migracje w EF Core i jak zarządzać nimi na produkcji? łatwe +

Migracje to kod C# opisujący zmiany schematu bazy danych. Generowane automatycznie z modelu, mogą być wykonywane przez EF lub ręcznie jako skrypt SQL.

dotnet ef migrations add AddOrderStatusColumn dotnet ef database update// Generowanie skryptu SQL (bezpieczne na produkcji) dotnet ef migrations script –idempotent -o migration.sql// Automatyczne migracje przy starcie — TYLKO dev/staging, NIE na produkcji! using var scope = app.Services.CreateScope(); await scope.ServiceProvider .GetRequiredService<AppDbContext>() .Database.MigrateAsync();
Produkcja: generuj idempotent script, przejrzyj z DBA, wykonaj w transakcji, zrób backup przed migracją. Nigdy MigrateAsync() bezpośrednio na produkcji.
33 Co to jest AsNoTracking() i kiedy go używać? łatwe +

Domyślnie EF Core śledzi zmiany w załadowanych encjach (Change Tracker). AsNoTracking() wyłącza śledzenie — szybsze zapytania, mniejsze zużycie pamięci. Stosuj dla operacji tylko do odczytu.

// Read-only — śledzenie niepotrzebne var orders = await ctx.Orders .AsNoTracking() .Where(o => o.IsActive) .ToListAsync(); // ~30% szybciej, mniejszy footprint pamięci// Globalne ustawienie dla całego kontekstu ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;// Tracking jest potrzebny gdy zapisujesz zmiany: var order = await ctx.Orders.FindAsync(id); // ← tracked order.Status = “Completed”; await ctx.SaveChangesAsync();
34 Jak działa DbContext i dlaczego powinien być Scoped? średnie +

DbContext nie jest thread-safe i reprezentuje wzorzec Unit of Work — śledzi zmiany w ramach jednej operacji biznesowej. Scoped = jeden kontekst na żądanie HTTP.

// Domyślnie Scoped builder.Services.AddDbContext<AppDbContext>(opt => opt.UseSqlServer(connectionString));// NIE używaj Singleton — współdzielony stan między równoległymi żądaniami // NIE używaj Transient — każde repository dostaje inny kontekst (brak UoW)// Scoped gwarantuje ten SAM DbContext w całym żądaniu: public class OrderRepository(AppDbContext ctx) : IOrderRepository { } public class CustomerRepository(AppDbContext ctx) : ICustomerRepository { } // ctx w obu = ten sam obiekt dzięki Scoped lifetime
35 Co to jest Repository Pattern i czy zawsze warto go stosować z EF Core? średnie +

Repository abstrakcjonizuje dostęp do danych — ułatwia testowanie i pozwala zmienić ORM bez dotykania logiki biznesowej. Kontrowersja: DbContext sam realizuje już wzorce Unit of Work i Repository.

public interface IOrderRepository { Task<Order?> GetByIdAsync(int id, CancellationToken ct = default); Task<List<Order>> GetActiveAsync(CancellationToken ct = default); void Add(Order order); void Remove(Order order); }public class SqlOrderRepository(AppDbContext ctx) : IOrderRepository { public Task<Order?> GetByIdAsync(int id, CancellationToken ct) => ctx.Orders.AsNoTracking().FirstOrDefaultAsync(o => o.Id == id, ct);public void Add(Order order) => ctx.Orders.Add(order); // SaveChangesAsync wywołuje Unit of Work / Service layer }
Kompromis: dla małych projektów bezpośredni DbContext w serwisach jest akceptowalny. Dla dużych — Repository + Unit of Work dają testowalność i izolację warstwy danych.
36 Kiedy użyć raw SQL zamiast LINQ w EF Core? średnie +

LINQ jest pierwszym wyborem. Raw SQL stosujemy gdy: LINQ generuje nieefektywne zapytanie, potrzebujemy zaawansowanych funkcji SQL (CTE, window functions) lub optymalizujemy krytyczną ścieżkę wydajnościową.

// EF Core 7+ — FromSql z interpolacją (parametryzowane, bezpieczne na SQL Injection) var orders = await ctx.Orders .FromSql($”SELECT * FROM Orders WHERE CustomerId = {customerId}”) .ToListAsync();// ExecuteSqlRaw — DML (INSERT/UPDATE/DELETE) await ctx.Database.ExecuteSqlRawAsync( “UPDATE Orders SET Status = {0} WHERE CreatedAt < {1}”, “Archived”, DateTime.UtcNow.AddYears(-2));// Dapper — pełna kontrola SQL + projekcja na dowolne DTO using var conn = ctx.Database.GetDbConnection(); var dtos = await conn.QueryAsync<OrderSummaryDto>( “SELECT o.Id, c.Name, SUM(i.Price) AS Total FROM Orders o …”);
Nigdy nie konkatenuj stringa z danymi użytkownika w SQL. Zawsze parametryzuj — EF robi to automatycznie przy interpolacji $"...{variable}" w FromSql.
37 Co to jest optymistyczne i pesymistyczne blokowanie współbieżności? trudne +

Optimistic Locking — zakładamy brak konfliktu, weryfikujemy przy zapisie przez RowVersion/ETag. Pessimistic Locking — blokujemy rekord na czas transakcji (SELECT FOR UPDATE). EF Core wspiera optymistyczne blokowanie natywnie przez [Timestamp].

public class Order { public int Id { get; set; } [Timestamp] // EF doda WHERE RowVersion = @original w UPDATE public byte[] RowVersion { get; set; } = null!; }try { await ctx.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) { var entry = ex.Entries.Single(); var dbValues = await entry.GetDatabaseValuesAsync(); // Decyzja: odrzuć, scal lub nadpisz — zależy od reguł biznesowych throw new ConflictException(“Zamówienie zostało zmienione przez innego użytkownika.”); }
ARC Wzorce Projektowe & Architektura Q38–Q43
38 Wyjaśnij wzorzec Repository + Unit of Work. średnie +

Repository izoluje dostęp do danych. Unit of Work grupuje wiele operacji w jedną atomową transakcję — albo wszystkie się udają, albo wszystkie są cofane.

public interface IUnitOfWork : IDisposable { IOrderRepository Orders { get; } ICustomerRepository Customers { get; } Task<int> SaveChangesAsync(CancellationToken ct = default); }public class EfUnitOfWork(AppDbContext ctx) : IUnitOfWork { public IOrderRepository Orders { get; } = new SqlOrderRepository(ctx); public ICustomerRepository Customers { get; } = new SqlCustomerRepository(ctx); public Task<int> SaveChangesAsync(CancellationToken ct) => ctx.SaveChangesAsync(ct); public void Dispose() => ctx.Dispose(); }async Task PlaceOrder(CreateOrderRequest req) { var customer = await uow.Customers.GetByIdAsync(req.CustomerId); var order = Order.Create(customer, req.Items); uow.Orders.Add(order); await uow.SaveChangesAsync(); // jeden commit dla całej operacji }
39 Co to jest wzorzec Mediator i jak go używać z MediatR? średnie +

Mediator oddziela nadawców od odbiorców — żądania/zdarzenia przechodzą przez centralny bus. MediatR + CQRS to popularny fundament Clean Architecture w ekosystemie .NET.

// Command (mutuje stan) public record CreateOrderCommand(int CustomerId, List<OrderItem> Items) : IRequest<OrderDto>;// Handler — cała logika biznesowa w jednym miejscu public class CreateOrderHandler(IUnitOfWork uow, IMapper mapper) : IRequestHandler<CreateOrderCommand, OrderDto> { public async Task<OrderDto> Handle(CreateOrderCommand cmd, CancellationToken ct) { var order = Order.Create(cmd.CustomerId, cmd.Items); uow.Orders.Add(order); await uow.SaveChangesAsync(ct); return mapper.Map<OrderDto>(order); } }// Controller — cienki, deleguje logikę do Mediatora [HttpPost] public Task<ActionResult<OrderDto>> Create(CreateOrderCommand cmd, IMediator mediator) => mediator.Send(cmd);
Licencja MediatR (aktualizacja 2025/2026): od wersji 13.0 (lipiec 2025) MediatR jest na podwójnej licencji — RPL-1.5 (copyleft) albo licencja komercyjna. Darmowy pozostaje bezpłatny tier “Community” dla firm/osób poniżej 5 mln USD rocznego przychodu; powyżej tego progu wymagana jest płatna licencja. Wersja 12.5.0 zostaje na Apache 2.0 na zawsze, ale bez wsparcia i poprawek bezpieczeństwa. Na rozmowie kwalifikacyjnej warto umieć wskazać alternatywy: własny lekki mediator oparty na IPipelineBehavior-podobnym wzorcu (kilkadziesiąt linii kodu, zero zależności) albo bibliotekę Mediator (source generator, bez narzutu refleksji, w pełni darmowa) — zwłaszcza jeśli pipeline behaviors nie są realnie wykorzystywane, a mediator służy tylko jako cienka warstwa dispatchująca.
40 Co to jest Clean Architecture i jakie są jej warstwy? trudne +

Clean Architecture (Robert C. Martin) organizuje kod w koncentryczne warstwy z regułą zależności (Dependency Rule): zależności wskazują wyłącznie do środka. Warstwy wewnętrzne nie wiedzą nic o zewnętrznych.

// Warstwy (od środka do zewnątrz): // Domain — Entities, Value Objects, Domain Events; zero zewnętrznych zależności // Application — Use Cases, Commands, Queries, Interfejsy; zależy od Domain // Infrastructure — EF Core, Email, Storage; implementuje interfejsy z Application // Presentation — Controllers, Minimal API; zależy od Application// src/ // MyApp.Domain/ Entities/Order.cs, Events/OrderPlaced.cs // MyApp.Application/ Orders/Commands/CreateOrderCommand.cs // Interfaces/IOrderRepository.cs // MyApp.Infrastructure/ Persistence/SqlOrderRepository.cs // MyApp.Api/ Controllers/OrdersController.cs// Infrastructure zależy od Application (nie odwrotnie): class SqlOrderRepository : IOrderRepository // IOrderRepository żyje w Application!
Na rozmowie: wystarczy, że rozumiesz Dependency Rule i potrafisz uzasadnić, dlaczego logika biznesowa nie powinna wiedzieć o SQL ani HTTP.
41 Co to jest wzorzec Factory i kiedy go używać? łatwe +

Factory enkapsuluje logikę tworzenia obiektów. Używamy gdy: tworzenie jest złożone lub zawiera reguły biznesowe, musimy wybrać konkretny typ w runtime, lub chcemy ukryć szczegóły konstruktora.

public class Order { private Order() { } // prywatny konstruktor — wymuszamy użycie metody fabrycznejpublic static Order Create(Customer customer, IEnumerable<OrderItem> items) { if (!customer.IsActive) throw new DomainException(“Nieaktywny klient”); if (!items.Any()) throw new DomainException(“Brak pozycji w zamówieniu”);return new Order { CustomerId = customer.Id, Items = items.ToList(), Status = OrderStatus.Pending, CreatedAt = DateTime.UtcNow }; } }// Abstract Factory — rodzina powiązanych obiektów public interface INotificationFactory { IEmailSender CreateEmailSender(); ISmsSender CreateSmsSender(); }
42 Czym jest wzorzec Decorator i gdzie jest stosowany w .NET? średnie +

Decorator owija istniejący obiekt, dodając nowe zachowanie bez modyfikowania klasy bazowej. Doskonały do cross-cutting concerns: caching, logowanie, retry, pomiar czasu.

public class CachedOrderRepository( IOrderRepository inner, IMemoryCache cache) : IOrderRepository { public async Task<Order?> GetByIdAsync(int id, CancellationToken ct) { var key = $”order_{id}”; if (cache.TryGetValue(key, out Order? cached)) return cached;var order = await inner.GetByIdAsync(id, ct); if (order is not null) cache.Set(key, order, TimeSpan.FromMinutes(5));return order; } // pozostałe metody delegują do inner }// Rejestracja dekoratora w DI builder.Services.AddScoped<SqlOrderRepository>(); builder.Services.AddScoped<IOrderRepository>(sp => new CachedOrderRepository( sp.GetRequiredService<SqlOrderRepository>(), sp.GetRequiredService<IMemoryCache>()));
43 Co to jest Result Pattern i kiedy jest lepszy od wyjątków? trudne +

Result Pattern jawnie komunikuje sukces lub błąd przez typ zwracany, zamiast rzucać wyjątki dla przewidywalnych scenariuszy. Wyjątki są kosztowne i utrudniają śledzenie przepływu sterowania.

public class Result<T> { public bool IsSuccess { get; } public T? Value { get; } public string? Error { get; }private Result(T value) { IsSuccess = true; Value = value; } private Result(string error) { IsSuccess = false; Error = error; }public static Result<T> Ok(T value) => new(value); public static Result<T> Fail(string msg) => new(msg); }public async Task<Result<OrderDto>> CreateOrderAsync(CreateOrderRequest req) { var customer = await repo.GetCustomerAsync(req.CustomerId); if (customer is null) return Result<OrderDto>.Fail($”Klient {req.CustomerId} nie istnieje”);if (!customer.HasCreditLimit(req.Total)) return Result<OrderDto>.Fail(“Przekroczony limit kredytowy”);return Result<OrderDto>.Ok(dto); }// Controller var result = await service.CreateOrderAsync(req); return result.IsSuccess ? Ok(result.Value) : BadRequest(result.Error);
Kiedy wyjątki? Sytuacje naprawdę wyjątkowe: brak połączenia z DB, błąd I/O. Kiedy Result? Oczekiwane błędy biznesowe: brak encji, naruszenie reguły domenowej.
TEST Testowanie Q44–Q47
44 Co to jest test jednostkowy vs integracyjny vs e2e? Kiedy który? łatwe +

Unit test — testuje izolowaną jednostkę kodu, zależności mockowane. Szybki, deterministyczny. Integration test — testuje współpracę komponentów (np. kod + baza danych). E2E — symuluje użytkownika przez cały system (Playwright, Selenium).

// Unit test — xUnit + FluentAssertions + NSubstitute public class OrderServiceTests { private readonly IOrderRepository _repo = Substitute.For<IOrderRepository>(); private readonly OrderService _sut;public OrderServiceTests() => _sut = new OrderService(_repo);[Fact] public async Task CreateOrder_WhenCustomerInactive_ThrowsDomainException() { // Arrange var customer = new Customer { IsActive = false }; _repo.GetByIdAsync(Arg.Any<int>()).Returns(customer);// Act var act = () => _sut.CreateOrderAsync(new CreateOrderRequest());// Assert await act.Should().ThrowAsync<DomainException>() .WithMessage(“*nieaktywny*”); } }
Test Pyramid: dużo unit testów (fast, cheap), mniej integracyjnych, bardzo mało e2e (slow, expensive). Złota proporcja: 70/20/10.
45 Co to jest mockowanie i jaka jest różnica między Mock, Stub a Fake? średnie +

Stub — zwraca predefiniowane dane, nie weryfikuje wywołań. Mock — weryfikuje, że metoda została wywołana z właściwymi argumentami. Fake — działająca, uproszczona implementacja (np. baza in-memory).

using NSubstitute;var repo = Substitute.For<IOrderRepository>();// Stub — konfiguracja zwracanej wartości repo.GetByIdAsync(1).Returns(new Order { Id = 1 });// Weryfikacja wywołania (mock behavior) await svc.DeleteOrderAsync(1); await repo.Received(1).DeleteAsync(Arg.Is<Order>(o => o.Id == 1));// Fake — działająca implementacja in-memory public class InMemoryOrderRepository : IOrderRepository { private readonly List<Order> _orders = []; public Task<Order?> GetByIdAsync(int id, CancellationToken _) => Task.FromResult(_orders.FirstOrDefault(o => o.Id == id)); public void Add(Order o) => _orders.Add(o); }
46 Jak testować Web API integracyjnie w ASP.NET Core? trudne +

WebApplicationFactory<T> uruchamia in-process prawdziwy serwer ASP.NET Core — pełny pipeline middleware, DI, routing. Bez mockowania HTTP. Bazę danych wymieniamy na in-memory lub Testcontainers.

public class OrdersApiTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>> { private readonly HttpClient _client = factory .WithWebHostBuilder(b => b.ConfigureServices(svc => svc.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase(“TestDb”)))) .CreateClient();[Fact] public async Task GET_Orders_ReturnsOk() { var response = await _client.GetAsync(“/api/orders”); response.StatusCode.Should().Be(HttpStatusCode.OK); var orders = await response.Content.ReadFromJsonAsync<List<OrderDto>>(); orders.Should().NotBeNull(); } }
47 Co to jest AAA (Arrange-Act-Assert) i jak pisać czytelne testy? łatwe +

AAA to struktura testu: przygotuj dane → wykonaj akcję → zweryfikuj wynik. Czytelny test = żywa dokumentacja zachowania kodu.

[Theory] [InlineData(100, 0.1, 90)] // kwota, procent rabatu, oczekiwany wynik [InlineData(200, 0.2, 160)] public void ApplyDiscount_ReturnsCorrectTotal( decimal total, double discount, decimal expected) { // Arrange var order = new Order { Total = total }; var policy = new PercentageDiscount(discount);// Act var result = policy.Apply(order.Total);// Assert result.Should().Be(expected); }// Konwencja nazewnictwa: MethodName_Scenario_ExpectedBehavior // void PlaceOrder_WhenInsufficientStock_ThrowsOutOfStockException()
FluentAssertions poprawiają czytelność i komunikaty błędów: result.Should().Be(90) zamiast Assert.Equal(90, result).
GIT Git, DevOps & Dobre Praktyki Q48–Q50
48 Jaka jest różnica między git merge a git rebase? średnie +

merge łączy gałęzie, zachowując pełną historię (tworzy merge commit). rebase przepisuje historię tak, jakby branch był stworzony z aktualnego HEAD — liniowa, czysta historia.

# Merge — zachowuje historię, bezpieczny git checkout main git merge feature/orders # Tworzy merge commit widoczny w historii# Rebase — czysta, liniowa historia git checkout feature/orders git rebase main # Przenosi commity feature na wierzchołek main, potem fast-forward# Złota zasada rebase: # NIE rebasuj commitów już opublikowanych na współdzielone branche! # Rebase stosuj tylko na prywatnych/lokalnych branchach.
W praktyce: rebase feature branch na main przed PR = czysta historia. Merge na main = zawsze (zachowuje punkt integracji).
49 Co to jest CI/CD i jak wygląda typowy pipeline w .NET? średnie +

CI (Continuous Integration) — automatyczna weryfikacja kodu przy każdym push (build + testy). CD (Continuous Delivery/Deployment) — automatyczne wdrożenie do środowisk po przejściu CI.

# .github/workflows/ci.yml — przykład GitHub Actions dla .NET name: CI/CD on: [push, pull_request]jobs: build-and-test: runs-on: ubuntu-latest steps: – uses: actions/checkout@v4 – uses: actions/setup-dotnet@v4 with: dotnet-version: ‘9.x’– name: Restore run: dotnet restore– name: Build run: dotnet build –no-restore –configuration Release– name: Test run: dotnet test –no-build –configuration Release –logger trx– name: Publish if: github.ref == ‘refs/heads/main’ run: dotnet publish -c Release -o ./publish– name: Deploy to Azure if: github.ref == ‘refs/heads/main’ uses: azure/webapps-deploy@v3 with: app-name: my-dotnet-app publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}
50 Jak podejść do code review — co sprawdzasz i jak dawać feedback? łatwe +

Code review to nie polowanie na błędy — to dzielenie się wiedzą i podnoszenie jakości całego zespołu. Dobry review patrzy na czytelność, testy, bezpieczeństwo i zgodność z architekturą.

// Checklist — co sprawdzam w każdym review: // ✅ Czy kod jest czytelny bez komentarzy? (self-documenting code) // ✅ Czy nazwy metod i zmiennych wyrażają intencję? // ✅ Czy są testy — i czy testują właściwe scenariusze? // ✅ Czy brak N+1, nieobsłużonych wyjątków, podatności SQL Injection? // ✅ Czy nie ma zbędnych zależności i over-engineeringu? // ✅ Czy zmiana jest zgodna z architekturą projektu? // ✅ Czy żadne sekrety/klucze API nie trafiły do kodu?// Jak dawać konstruktywny feedback: // ❌ “To jest złe.” // ✅ “Rozważyłbym tu IEnumerable zamiast List — zewnętrzny kod // nie powinien modyfikować wewnętrznej kolekcji. Co sądzisz?”// Prefiksy komentarzy (Conventional Comments): // nit: drobiazg, nieblokujące // suggestion: propozycja, nieblokujące // issue: rzeczywisty problem, blokuje merge // question: pytanie, nieoceniające
Na rozmowie: pokaż, że traktujesz review jako rozmowę, nie osąd. Pytasz “dlaczego?” zanim zaproponujesz zmianę — to dojrzałość dobrego teamplayera.
auth.devhobby — sesja gościa
GET /top-50-pytan 401 Unauthorized // wymagany: dostęp VIP (bezpłatny, na zawsze) // w pakiecie: roadmapa .NET w PDF

Gotowe! Sprawdź skrzynkę.

Za chwilę dostaniesz e-mail z dostępem do roadmapy. Jesteś też na liście VIP.
Nie widzisz wiadomości? Sprawdź folder Spam.

Zero spamu. Odblokowanie działa od razu, roadmapa wpada na maila.

Otrzymując bezpłatnie Roadmapę .NET, zapisujesz się na newsletter dev-hobby.pl — materiały edukacyjne oraz informacje o produktach i promocjach. Możesz wypisać się w każdej chwili.

idź o krok dalej

Pytania to jedno. Umiejętności to drugie.

Pełny program Junior .NET Developer nauczy Cię nie tylko odpowiadać na pytania, ale przede wszystkim pisać kod, o który te pytania pytają. Zapisz się na listę VIP, aby wiedzieć o otwarciu zapisów.

Gotowe! Sprawdź skrzynkę.

Za chwilę dostaniesz e-mail z dostępem do roadmapy. Jesteś też na liście VIP.
Nie widzisz wiadomości? Sprawdź folder Spam.

Otrzymując bezpłatnie Roadmapę .NET, zapisujesz się na newsletter dev-hobby.pl — materiały edukacyjne oraz informacje o produktach i promocjach. Możesz wypisać się w każdej chwili.