Lessons available in both languages
Java Backend · Interview Prep

Spring Testing interview questions & answers

0+ real Spring Testing interview questions with model answers, plus free lessons to learn the concepts. Prepare in English & Hinglish, then practise with an AI mock interview.

18 topics · 0+ questions

Hirenix kaise padhata hai

Ek chapter. 90 minute.
Interview ke liye taiyaar.

Har concept ek real-world problem se — jaisa production code mein aata hai, waisa. Ratna nahi padta, samajh aa jaata hai. Har question ka model answer diya hai: interviewer ko exactly kya bolna hai, aur kyun. Phir usi chapter ka AI mock interview.

  • 📖Concept, 5 min meinJargon nahi — seedhi baat
  • 🛠️Real-world problemJaisa production code mein aata hai
  • 💬Model answerInterview mein kya bolna hai
  • 🧠FlashcardsRevision 10 min mein
  • 🤖AI mock interviewFollow-up bhi poochta hai
  • 📊Weak topicsKahan phans rahe ho, pata chale
Ye chapter shuru karo — free🌐 English🇮🇳 Hinglish
A student learning an interview concept on Hirenix at home
Video playlistsyllabus ke hisaab se18h+
Hirenix chapterinterview ke hisaab se90 min

Farq content ka nahi, filter ka hai — sirf wahi jo production mein actually use hota hai aur interview mein actually poocha jaata hai. Kitaabi topics jo industry mein kahin nahi chalte, wo yahan nahi milenge.

Lessons available in both languages

What you’ll learn

  • Spring apps ko test kyun karein: unit, slice aur full-context tests
  • JUnit 5 ki buniyaad: lifecycle, assertions aur parameterized tests
  • AssertJ aur aise assertions jo apni failure khud samjhayeinFree account
  • Mockito ki buniyaad: mock, stub, verify aur strict stubsFree account
  • SpringBootTest aur uske web environments: MOCK vs RANDOM_PORTFree account
  • Test slices: WebMvcTest kya load karta hai aur kya chhod deta haiFree account
  • MockMvc: request banana aur response ko assert karnaFree account
  • MockitoBean, deprecated MockBean, aur context me bean badalnaFree account
  • DataJpaTest: embedded database aur apne aap rollbackFree account
  • Spring Security ko test karna: WithMockUser, csrf aur denial testsFree account
  • Context caching: ek test suite tez aur doosri dheemi kyun hoti haiFree account
  • TestConfiguration, test properties aur profilesFree account
  • Testcontainers aur asli databases: jab H2 kaafi nahi hotaFree account
  • Transactional test ke jaal: galat wajah se pass hone wale testsFree account
  • RecapFree account
  • Project: Secure Job Board API ke tests likho
  • Project: DataJpaTest se Repository TestsFree account
  • Project: Dheemi Test Suite ko Tez KaroFree account

Spring apps ko test kyun karein: unit, slice aur full-context tests

Jo Spring test suites dheemi ya naazuk lagti hain, unki jad aksar ek hi hoti hai: har test poori application start karta hai, kyunki @SpringBootTest pehla annotation tha jo kisi ko mila. Ye topic is baare me hai ki sabse chhota test kaunsa hai jo sach me wo bug pakad sake jiski aapko chinta hai - aur ye sabit karna ki test fail bhi ho sakta hai.

Ek application, test ke teen level

Demo job board ka ek chhota hissa hai. ApplicationScorer me sirf logic hai (experience paanch par ruk jaata hai, Java aane par paanch aur judte hain), aur ScoreController use HTTP par deta hai. Wahi code chaar tarah test hota hai:

Level Kya banta hai Is run me naapa gaya
Unit - plain JUnit sirf new ApplicationScorer() pass; class me kahin Spring annotation nahi
Unit - Mockito controller aur MockitoExtension se ek @Mock scorer pass; ab bhi koi Spring annotation nahi, koi context nahi
Slice - @WebMvcTest sirf web layer; scorer ki jagah @MockitoBean 168 bean definitions
Full - @SpringBootTest poora application context 279 bean definitions

Ye gintiyan Spring Boot 3.5.16 par isi ek application ki hain, isi exact classpath par - inhe har jagah ka number mat bataiye. Ye is app ke liye bhi tay nahi hain: environment me Bean Validation (hibernate-validator) judne se pehle wahi do tests 165 aur 276 ginte the. Ek library, dono me teen definitions zyada. Shakal bataiye: slice ne poore context ka lagbhag 60% register kiya, us app me jiski apni sirf do classes hain. Asli service me repositories, HTTP clients aur schedulers hote hain, aur test ka samay isi farak me jaata hai. (Is environment me timing naapi nahi gayi, isliye ye topic speed ka koi dawa nahi karta.)

Har level kya pakad sakta hai aur kya nahi

  • Plain unit test niyam sabit karta hai: score(12, true) 10 hai kyunki experience ruk jaata hai. Wo nahi bata sakta ki controller /score par map hai, ya Spring bean bana bhi pa raha hai.
  • Mockito unit test sabit karta hai ki controller sahi tarah kaam aage deta hai. @ExtendWith(MockitoExtension.class) bina kisi container ke @Mock aur @InjectMocks deta hai - ye ab bhi unit test hai.
  • Slice web wiring sabit karta hai: request mapping, JSON, security chain, @ControllerAdvice. Controller ke neeche ki har cheez ko wo jaan-boojh kar mock se badal deta hai.
  • Full-context test ek integration test hai: asli beans aapas me jude hue. Teeno me se sirf yahi level fail hota hai jab koi bean ban hi na sake.
  • End-to-end test chalti hui system ko bahar se chalata hai - browser ya asli database ke saath asli HTTP. Is environment me browser nahi hai, isliye yahan ye sirf ek paribhasha hai.

Yahi test pyramid hai: neeche bahut saare unit tests, beech me kam slices aur integration tests, upar bahut kam end-to-end tests.

spring-boot-starter-test classpath par kya laata hai

spring-boot-starter-test 3.5.16 ka POM seedha padh kar (artifact padha gaya, chalaya nahi): spring-boot-test, spring-boot-test-autoconfigure, spring-test, JUnit Jupiter, mockito-core, mockito-junit-jupiter, AssertJ, Hamcrest, JsonPath, JSONassert, awaitility aur xmlunit-core. Ek test dependency, aur is chapter ka har auzaar maujood.

Jo test aapne kabhi fail hote nahi dekha, wo bahut kam sabit karta hai

Run 2 wahi assertion rakhta hai aur sirf expected value badalta hai, 10 se 11. Runner jawab deta hai expected: <11> but was: <10> aur [ 1 tests failed ]. Ise negative control kehte hain: kisi green test par bharosa karne se pehle use ek baar jaan-boojh kar todiye aur laal hote dekhiye. Is chapter me aage baar-baar aise tests milenge jo galat wajah se green hain, aur ye aadat unse sabse sasta bachav hai.

Kaunsa level kab use karein: sabse neeche wale us level se shuru kijiye jo bug dekh sake. Business rule -> plain unit test. Collaborators ko bulane wali class -> Mockito unit test. Request mapping, JSON ki shakal, validation ya security rules -> @WebMvcTest. Bean wiring, configuration, kai layers ek saath -> @SpringBootTest.

Kab NAHI @SpringBootTest lena chahiye: jab sawaal ek class ke logic ka ho. Aap poore context ki keemat chukate hain (yahan 279 definitions, jabki zaroorat zero ki thi) aur failure aapke tode hue niyam ki jagah application startup ki taraf ishara karta hai.

Trade-off: ye dono taraf kaatta hai. Sirf mocks se bani suite poori green ho sakti hai jabki asli application start hi na ho, kyunki mocks wiring kabhi jaanchte nahi. Isi kaam ke liye kuch full-context integration tests zaroor rakhiye.

// ---- t1/ApplicationScorer.java : pure logic, no database, no HTTP ----
@Service
public class ApplicationScorer {
  public int score(int yearsExperience, boolean knowsJava) {
    int s = Math.min(yearsExperience, 5);          // experience counts up to 5
    return knowsJava ? s + 5 : s;
  }
}

// ---- t1/ScoreController.java ----
@RestController
public class ScoreController {
  private final ApplicationScorer scorer;
  public ScoreController(ApplicationScorer scorer) { this.scorer = scorer; }
  @GetMapping("/score") public int score(@RequestParam int years, @RequestParam boolean java) { return scorer.score(years, java); }
}

// ---- Level 1: plain JUnit. No Spring, no context, just `new`. ----
class ScorerUnitTest {
  @Test void capsExperienceAtFive() {
    ApplicationScorer scorer = new ApplicationScorer();
    assertEquals(10, scorer.score(12, true));
    System.out.println("UNIT  score(12,true)=" + scorer.score(12, true) + "  (no ApplicationContext created)");
  }
}

// ---- Still level 1: a controller tested with Mockito alone. No Spring context is started. ----
@ExtendWith(MockitoExtension.class)
class ScoreControllerMockitoTest {
  @Mock ApplicationScorer scorer;
  @InjectMocks ScoreController controller;
  @Test void delegatesToScorer() {
    given(scorer.score(3, true)).willReturn(8);
    assertEquals(8, controller.score(3, true));
    verify(scorer).score(3, true);
    System.out.println("MOCKITO controller.score(3,true)=" + controller.score(3, true) + "  (no ApplicationContext created)");
  }
}

// ---- Level 2: a slice. Only the web layer is built; the scorer is replaced by a mock. ----
@WebMvcTest(ScoreController.class)
class ScorerSliceTest {
  @Autowired ApplicationContext ctx;
  @MockitoBean ApplicationScorer scorer;
  @Test void webLayerOnly() {
    System.out.println("SLICE beans=" + ctx.getBeanDefinitionCount()
        + "  controller=" + ctx.getBeanNamesForType(ScoreController.class).length);
  }
}

// ---- Level 3: the whole application context, real beans wired together. ----
@SpringBootTest
class ScorerFullTest {
  @Autowired ApplicationContext ctx;
  @Autowired ApplicationScorer scorer;
  @Test void wholeContext() {
    assertEquals(10, scorer.score(12, true));
    System.out.println("FULL  beans=" + ctx.getBeanDefinitionCount() + "  scorer=" + scorer.getClass().getSimpleName());
  }
}

// ---- t1neg: same assertion with a deliberately WRONG expectation - proves the runner really fails. ----
class ScorerNegativeControlTest {
  @Test void wrongExpectation() { assertEquals(11, new ApplicationScorer().score(12, true)); }
}

// Run with the JUnit Platform console launcher (Boot 3.5.16, JUnit 5.12.2, Mockito 5.17.0, JDK 17):
//   run 1: --select-class ScorerUnitTest ScoreControllerMockitoTest ScorerSliceTest ScorerFullTest
//   run 2: --select-class ScorerNegativeControlTest

JUnit 5 ki buniyaad: lifecycle, assertions aur parameterized tests

JUnit 5 ek akeli library nahi hai. Ye ek platform aur uske engines hain, aur pehle hafte ki lagbhag har bharmane wali baat - tests ka ajeeb kram me chalna, set kiya hua field achanak khaali milna, setup method jise JUnit chalane se mana kar de - isi dhaanche aur lifecycle se samajh aati hai. Neeche ki har cheez chalayi gayi hai, rati nahi gayi.

Platform, Jupiter, Vintage

Launcher ka chhapa hua tree dekhiye. Uski teen jadein hain: JUnit Platform Suite, JUnit Jupiter aur JUnit Vintage.

  • JUnit Platform tests dhoondhta hai, chalata hai aur nateeje batata hai. Build tools aur IDE tests isi ke zariye chalate hain (JUnit documentation ke mutabik - yahan koi build tool chalaya nahi gaya). Is chapter ke environment me koi build tool hai hi nahi - wo Platform ka console launcher seedha chalata hai, isiliye chapter ka har output [ 1 tests failed ] jaisi lines par khatam hota hai.
  • Jupiter JUnit 5 ke programming model ka engine hai: @Test, @BeforeEach, @ParameterizedTest, @Nested, @ExtendWith.
  • Vintage wo engine hai jo purane JUnit 4 tests usi Platform par chalata hai. Ye yahan isliye dikha kyunki standalone launcher use saath laata hai; use chalane ko kuch mila nahi.

Lifecycle, naapa hua

LifecycleTest ke output me teen baatein dikhti hain:

  1. @BeforeAll sabse pehle ek baar chala, aur @AfterAll bilkul aakhir me ek baar. Dono static hain.
  2. Har test method ke liye test class ka naya instance bana - do methods, do alag instance ids (210ab13f, 3b35a229). Ek test jo field set karta hai, wo agle test me hota hi nahi. Yahi default tests ko instance fields ke zariye state leak karne se rokta hai.
  3. @BeforeEach aur @AfterEach ne har test ko lapeta, usi test ke apne instance par.

@BeforeAll static kyun hona chahiye? Kyunki wo kisi bhi instance ke banne se pehle chalta hai. static hataiye aur JUnit class chalane se hi mana kar deta hai:

@BeforeAll method 'void t2bad.NonStaticBeforeAllTest.setUp()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).

Us class ke summary par dhyaan dijiye: 0 tests successful, 0 tests failed. Class container ki tarah fail hui, isliye uska test chala hi nahi - jis report me koi failure na dikhe, wo us report jaisi nahi hai jisme pass dikhein.

Kram wo nahi jo aapne likha

first pehle declare hua hai aur second baad me, par second pehle chala. Nested class me bhi spaces are rejected, null is rejected se pehle chala - phir se source ka ulta. JUnit 5 declaration ke kram ka waada nahi karta. Jo test sirf kisi aur test ke chalne ke baad pass ho, wo toota hua hai, aur @TestMethodOrder us kam hi aane wale case ke liye hai jahan kram khud test ka vishay ho - shared state ko dhakne ka tareeka nahi.

Assertions jo sahi failure jaanchein

assertThrows exception lautata hai, isliye type ke saath message bhi assert kijiye - rejectsMonthly exact text expected LPA, got: 50000 per month jaanchta hai. Jab code ummeed se alag type phenkta hai, failure bilkul saaf hota hai: Unexpected exception type thrown, expected: <java.lang.NullPointerException> but was: <java.lang.NumberFormatException>.

Ek niyam, kai input, padhne laayak report

@CsvSource ke saath @ParameterizedTest ne ek method ko teen alag report hone wale cases bana diya, har ek name pattern se naamit ("12 LPA" -> 1200000). Quote kiya hua ' 7 LPA ' dikhata hai ki CSV value me aage-peechhe ke space kaise bachayein. @Nested aur @DisplayName ne blank input wale cases ko ek padhne laayak heading ke neeche rakha, taaki tree ek specification jaisa padha jaaye.

JUnit 4 se JUnit 5 (documentation se - yahan chalaya nahi)

Is environment me JUnit 4 ka jar nahi hai, isliye ye mapping chala kar nahi, JUnit 5 documentation se hai: @Before/@After ab @BeforeEach/@AfterEach; @BeforeClass/@AfterClass ab @BeforeAll/@AfterAll; @RunWith(...) ab @ExtendWith(...) (pichhle topic ne @ExtendWith(MockitoExtension.class) chalaya tha); @Ignore ab @Disabled; @Test(expected = ...) ab assertThrows. Annotations org.junit se org.junit.jupiter.api me chale gaye.

@ParameterizedTest kab use karein: jab ek niyam inputs ki poori tabel par tikna chahiye - parsing, validation, seema wali values. Har row alag report hoti hai, isliye failure exact input ka naam leta hai.

@Nested kab use karein: jab test hone wali class ki alag-alag haalatein hon (blank input, sahi input, overflow) aur aap report bhi usi tarah bati hui chahte hon.

Kab NAHI @TestMethodOrder ya shared field use karna: ek test ko doosre ke bache hue data par tikaane ke liye. Naapa hua kram pehle hi source se alag hai; us par bharosa karne wali suite tab toot-ti hai jab koi naya method jodta hai.

Trade-off: error message khud @TestInstance(Lifecycle.PER_CLASS) ko non-static @BeforeAll ka raasta batata hai. JUnit documentation ke mutabik (yahan chalaya nahi) wo poori class ke liye ek hi instance use karta hai - mehnge setup ke liye aasaan, par upar naapa gaya har-test-naya-instance wala alagaav chhod deta hai.

// ---- t2/SalaryParser.java : "12 LPA" -> 1200000 rupees per year ----
public class SalaryParser {
  public static long parse(String text) {
    if (text == null || text.isBlank()) throw new IllegalArgumentException("salary is blank");
    String t = text.trim();
    if (!t.endsWith("LPA")) throw new IllegalArgumentException("expected LPA, got: " + t);
    double lakhs = Double.parseDouble(t.substring(0, t.length() - 3).trim());
    return Math.round(lakhs * 100_000);
  }
}

// ---- t2/LifecycleTest.java ----
class LifecycleTest {
  LifecycleTest() { System.out.println("  new LifecycleTest instance@" + Integer.toHexString(System.identityHashCode(this))); }
  @BeforeAll static void beforeAll() { System.out.println("@BeforeAll (static, once)"); }
  @BeforeEach void beforeEach() { System.out.println("  @BeforeEach on instance@" + Integer.toHexString(System.identityHashCode(this))); }
  @Test void first()  { System.out.println("    test first"); }
  @Test void second() { System.out.println("    test second"); }
  @AfterEach void afterEach() { System.out.println("  @AfterEach"); }
  @AfterAll static void afterAll() { System.out.println("@AfterAll (static, once)"); }
}

// ---- t2/SalaryParserTest.java ----
@DisplayName("SalaryParser")
class SalaryParserTest {
  @ParameterizedTest(name = "\"{0}\" -> {1}")
  @CsvSource({ "12 LPA, 1200000", "4.5 LPA, 450000", "' 7 LPA ', 700000" })
  void parsesLakhs(String text, long expected) { assertEquals(expected, SalaryParser.parse(text)); }

  @Test @DisplayName("rejects a monthly figure, with a useful message")
  void rejectsMonthly() {
    IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> SalaryParser.parse("50000 per month"));
    assertEquals("expected LPA, got: 50000 per month", e.getMessage());
  }

  @Nested @DisplayName("when the input is blank")
  class WhenBlank {
    @Test @DisplayName("null is rejected")  void nullInput()  { assertThrows(IllegalArgumentException.class, () -> SalaryParser.parse(null)); }
    @Test @DisplayName("spaces are rejected") void spaces()   { assertThrows(IllegalArgumentException.class, () -> SalaryParser.parse("   ")); }
  }
}

// ---- t2bad/NonStaticBeforeAllTest.java ----
class NonStaticBeforeAllTest {
  @BeforeAll void setUp() { }          // not static
  @Test void anything() { }
}

// ---- t2bad/WrongExceptionTest.java ----
class WrongExceptionTest {
  @Test void expectsNpeButGetsNumberFormat() {
    assertThrows(NullPointerException.class, () -> Double.parseDouble("twelve"));
  }
}

// Each class run separately with the JUnit Platform 1.12.2 console launcher (Jupiter 5.12.2, JDK 17).

Project: Secure Job Board API ke tests likho

Spring Security chapter ne ek job board API secure ki thi aur har rule chalti server ko haath se bula kar sabit kiya tha: anonymous POST 401, plain user 403, recruiter 200, admin delete 200. Wo saboot chhapte hi purana ho gaya. Ye project wahi saboot ek test suite ki tarah likhta hai jo har build par chalti hai - aur phir configuration ko do baar todta hai taaki dikhe ki theek kaunse tests dhyaan dete hain.

Test hone wale rules

Endpoint Rule
GET /api/jobs public
POST /api/jobs sirf RECRUITER
DELETE /api/jobs/** sirf ADMIN
GET /api/jobs/mine apna koi rule nahi - anyRequest().authenticated() tak jaata hai

SecurityConfig apni @Configuration class hai, application class par @Bean methods ka set nahi. Transactional-pitfalls wale topic ne wajah naapi thi: main class ke beans har test slice me load hote hain, aur web security bean JPA slice tod deta hai.

Har rule ke liye ek ijaazat wala aur ek denial test

JobBoardRulesTest controller ka @Import(SecurityConfig.class) ke saath @WebMvcTest hai, aur har "kaun kar sakta hai" ko "kaun nahi kar sakta" ke saath jodta hai:

  • feed: anonymous GET 200;
  • posting: anonymous 401, USER 403, RECRUITER 200 - aur jsonPath("$.postedBy") rec hai, jo sabit karta hai ki job caller ki identity ki hai, kisi request parameter ki nahi;
  • deleting: RECRUITER 403, ADMIN 200;
  • "mine": anonymous 401; aur recruiter ki job recruiter ki list me dikhti hai par asha ki me nahi.

Run 1: saare 8 green. Abhi tak ye sirf wahi hai jo haath ki jaanch pehle dikha chuki thi.

Todna 1 - ek rule dheela karo

-Ddemo.widen=true delete rule ko hasRole("ADMIN") se hasAnyRole("ADMIN", "RECRUITER") kar deta hai - ek line ka wo badlav jo review se nikal jaata hai. Run 2: 7 ab bhi green, adminCanDelete samet. Theek ek test fail hua:

recruiterCannotDelete() [X] Status expected:<403> but was:<200>

Denial tests ki poori daleel yahi hai. "Admin delete kar sakta hai" wala test us rule ko notice nahi kar sakta jo zyada logon ko andar aane de. Sirf wahi test kar sakta hai jo rule se kisi ko mana karwaye.

Todna 2 - import bhool jaao

NoImportRulesTest wahi feed test hai bina @Import(SecurityConfig.class) ke. Run 3:

feedIsPublic() [X] Status expected:<200> but was:<401>

Import ke bina slice controller ko Spring Boot ke default chain se secure karta hai (test-slices wale topic me naapa), jahan kuch bhi public nahi hai. Yahan galti ittefaq se zor se dikhi. Chup wala roop bura hai: jo suite sirf authenticated calls test kare wo default chain par pass ho jaayegi aur aapka ek bhi rule test nahi karegi.

@WithMockUser ne kya chhoda

Upar ke har MockMvc test ne security context me ek taiyaar user rakha. Kisi ne nahi jaancha ki rec ka BCrypt password sach me chalta hai. RealCredentialsTest random port par asli server start karta hai aur asli HTTP Basic credentials bhejta hai: rec:pw2 200, rec:nope 401, asha:pw1 403. Ye authentication ka raasta hai - UserDetailsService, PasswordEncoder, Basic filter - shuru se aakhir tak, ek baar. Rule tests slice me tez rehte hain; ek asli-HTTP test wo cover karta hai jo wo nahi kar sakte.

Saath le jaane wali shakal

Har access rule ke liye kam se kam ek test rakhiye jo mana hone (401 ya 403) ki ummeed kare, asli security configuration slice me import kijiye, aur kuch asli-credential tests HTTP par jodiye. Saath milkar ye tab fail hote hain jab rule chaudaa ho, jab configuration gayab ho, aur jab authentication khud toote - wo teen tareeke jinse secured API chup-chaap secured rehna chhod deti hai.

// ---- p16/JobController.java : the job board API from the Spring Security chapter, same behaviour ----
record Job(Long id, String title, String city, String postedBy, String status) {}

@RestController public class JobController {
  private final Map<Long, Job> jobs = new ConcurrentHashMap<>();
  private final AtomicLong seq = new AtomicLong();
  @GetMapping("/api/jobs") public Collection<Job> feed() { return jobs.values(); }
  @PostMapping("/api/jobs") public Job post(@RequestParam String title, @RequestParam String city, Authentication a) {
    Job j = new Job(seq.incrementAndGet(), title, city, a.getName(), "OPEN"); jobs.put(j.id(), j); return j;
  }
  @GetMapping("/api/jobs/mine") public List<Job> mine(Authentication a) {
    return jobs.values().stream().filter(j -> j.postedBy().equals(a.getName())).toList();
  }
  @DeleteMapping("/api/jobs/{id}") public String delete(@PathVariable Long id) { return jobs.remove(id) == null ? "not-found" : "deleted"; }
}

// ---- p16/SecurityConfig.java : its own @Configuration, NOT on the main class ----
@Configuration public class SecurityConfig {
  @Bean SecurityFilterChain chain(HttpSecurity h) throws Exception {
    boolean widened = Boolean.getBoolean("demo.widen");          // simulates a careless edit to one rule
    return h.csrf(c -> c.disable())
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(a -> {
          a.requestMatchers(HttpMethod.GET, "/api/jobs").permitAll();
          a.requestMatchers(HttpMethod.POST, "/api/jobs").hasRole("RECRUITER");
          if (widened) a.requestMatchers(HttpMethod.DELETE, "/api/jobs/**").hasAnyRole("ADMIN", "RECRUITER");
          else         a.requestMatchers(HttpMethod.DELETE, "/api/jobs/**").hasRole("ADMIN");
          a.anyRequest().authenticated();
        })
        .httpBasic(b -> {}).build();
  }
  @Bean PasswordEncoder enc() { return new BCryptPasswordEncoder(); }
  @Bean UserDetailsService users(PasswordEncoder e) {
    return new InMemoryUserDetailsManager(
        User.withUsername("asha").password(e.encode("pw1")).roles("USER").build(),
        User.withUsername("rec").password(e.encode("pw2")).roles("RECRUITER").build(),
        User.withUsername("boss").password(e.encode("pw3")).roles("ADMIN").build());
  }
}

// ---- p16/JobBoardRulesTest.java : one allowed test and one denial test per rule ----
@WebMvcTest(JobController.class)
@Import(SecurityConfig.class)
class JobBoardRulesTest {
  @Autowired MockMvc mvc;

  // GET /api/jobs : public
  @Test void feedIsPublic() throws Exception { mvc.perform(get("/api/jobs")).andExpect(status().isOk()); }

  // POST /api/jobs : RECRUITER only
  @Test void anonymousCannotPost() throws Exception {
    mvc.perform(post("/api/jobs").param("title", "Java Dev").param("city", "Pune")).andExpect(status().isUnauthorized());
  }
  @Test @WithMockUser(username = "asha", roles = "USER") void userCannotPost() throws Exception {
    mvc.perform(post("/api/jobs").param("title", "Java Dev").param("city", "Pune")).andExpect(status().isForbidden());
  }
  @Test @WithMockUser(username = "rec", roles = "RECRUITER") void recruiterCanPostAndOwnsTheJob() throws Exception {
    mvc.perform(post("/api/jobs").param("title", "Java Dev").param("city", "Pune"))
       .andExpect(status().isOk()).andExpect(jsonPath("$.postedBy").value("rec"));
  }

  // DELETE /api/jobs/** : ADMIN only
  @Test @WithMockUser(username = "rec", roles = "RECRUITER") void recruiterCannotDelete() throws Exception {
    mvc.perform(delete("/api/jobs/1")).andExpect(status().isForbidden());
  }
  @Test @WithMockUser(username = "boss", roles = "ADMIN") void adminCanDelete() throws Exception {
    mvc.perform(delete("/api/jobs/999")).andExpect(status().isOk()).andExpect(content().string("not-found"));
  }

  // GET /api/jobs/mine : no rule of its own, falls to anyRequest().authenticated()
  @Test void anonymousCannotSeeMine() throws Exception { mvc.perform(get("/api/jobs/mine")).andExpect(status().isUnauthorized()); }
  @Test void mineShowsOnlyTheCallersJobs() throws Exception {
    mvc.perform(post("/api/jobs").param("title", "Rec Only Role").param("city", "Delhi").with(user("rec").roles("RECRUITER")))
       .andExpect(status().isOk());
    mvc.perform(get("/api/jobs/mine").with(user("rec").roles("RECRUITER")))
       .andExpect(jsonPath("$[*].title", hasItem("Rec Only Role")));
    mvc.perform(get("/api/jobs/mine").with(user("asha").roles("USER")))
       .andExpect(jsonPath("$[*].title", not(hasItem("Rec Only Role"))));
  }
}

// ---- p16/NoImportRulesTest.java ----
@WebMvcTest(JobController.class)            // @Import(SecurityConfig.class) forgotten
class NoImportRulesTest {
  @Autowired MockMvc mvc;
  @Test void feedIsPublic() throws Exception { mvc.perform(get("/api/jobs")).andExpect(status().isOk()); }
}

// ---- p16/RealCredentialsTest.java : the part @WithMockUser skips ----
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class RealCredentialsTest {
  @Autowired TestRestTemplate http;
  @Test void bcryptUsersOverRealHttp() {
    int good  = http.withBasicAuth("rec", "pw2").postForEntity("/api/jobs?title=JavaDev&city=Pune", null, String.class).getStatusCode().value();
    int wrong = http.withBasicAuth("rec", "nope").postForEntity("/api/jobs?title=JavaDev&city=Pune", null, String.class).getStatusCode().value();
    int user  = http.withBasicAuth("asha", "pw1").postForEntity("/api/jobs?title=JavaDev&city=Pune", null, String.class).getStatusCode().value();
    System.out.println("P16 real HTTP POST: rec:pw2 -> " + good + " | rec:nope -> " + wrong + " | asha:pw1 -> " + user);
    assertEquals(200, good); assertEquals(401, wrong); assertEquals(403, user);
  }
}

// run 1: JobBoardRulesTest   run 2: same with -Ddemo.widen=true   run 3: NoImportRulesTest   run 4: RealCredentialsTest
// Boot 3.5.16, Spring Security 6.5.11 + spring-security-test, JUnit 5.12.2, JDK 17.

Ready to practise Spring Testing?

Unlock every topic free, then face an AI interviewer that asks follow-ups and grades your answers.