{"id":"code-dev-junit5-three-named-money-tests","audience":"developers","category":"coding","track":"coding","technique":"constraints","title":"Write 3 named JUnit 5 tests with plain assertions","free":false,"task":"Write MoneyTest for Money.parse with exactly 3 @Test methods named parsesThousandsSeparator (\"$1,234.50\" gives 123450 cents), rejectsMissingDollarSign (\"12.00\" throws IllegalArgumentException with message \"missing $\") and rejectsThreeDecimals (\"$1.999\" throws with \"too many decimals\"). Your team uses plain JUnit 5 only: no AssertJ, Hamcrest, Mockito or parameterized tests. Exactly 1 code block of at most 26 lines, under 20 words of text.","materialLabel":"The class (attached to your prompt automatically)","material":"// src/main/java/com/pennywise/core/Money.java  (Java 21, Gradle, JUnit 5.10)\npackage com.pennywise.core;\n\nimport java.math.BigDecimal;\n\npublic record Money(long cents) {\n\n    public static Money parse(String text) {\n        if (text == null || text.isBlank()) {\n            throw new IllegalArgumentException(\"blank amount\");\n        }\n        String t = text.trim();\n        if (!t.startsWith(\"$\")) {\n            throw new IllegalArgumentException(\"missing $\");\n        }\n        BigDecimal value = new BigDecimal(t.substring(1).replace(\",\", \"\"));\n        if (value.scale() > 2) {\n            throw new IllegalArgumentException(\"too many decimals\");\n        }\n        return new Money(value.movePointRight(2).longValueExact());\n    }\n}\n\n// build.gradle (test deps):\n//   testImplementation \"org.junit.jupiter:junit-jupiter:5.10.2\"\n//   (assertj was removed in September to keep the build lean)\n//\n// Test file goes in src/test/java/com/pennywise/core/MoneyTest.java\n// Existing tests in the repo use: import static org.junit.jupiter.api.Assertions.*;","rules":[{"type":"codeBlocks","count":1},{"type":"maxCodeLines","max":26},{"type":"maxProseWords","max":20}],"mistakes":[{"title":"A big test class","why":"The AI writes 8 to 12 tests including blank input, negatives and rounding.","fix":"Say: \"Exactly 3 @Test methods\" and name them.","checks":["r0","maxCodeLines"]},{"title":"Not checking the message","why":"The AI often asserts only the exception type, which passes even when the wrong check fires.","fix":"Add: \"Check the exception message: 'missing $' and 'too many decimals'.\"","checks":["r1"]},{"title":"Libraries you don't have","why":"The AI likes AssertJ's assertThat, which was removed from this build.","fix":"Add: \"Plain JUnit 5 Assertions only. No AssertJ, Hamcrest or Mockito.\"","checks":["r2"]},{"title":"An explanation of each test","why":"The AI explains the tests and how to run Gradle.","fix":"Add: \"One code block, no text.\"","checks":["codeBlocks","maxProseWords"]}],"promptHints":[{"label":"Sets the number of tests","patterns":["\\b(3|three)\\b.{0,15}test","exactly \\d"]},{"label":"Names the tests","patterns":["parsesThousandsSeparator","rejectsMissingDollarSign","rejectsThreeDecimals"]},{"label":"Checks the messages","patterns":["message","missing \\$","too many decimals"]},{"label":"Rules out AssertJ","patterns":["assertj","plain junit","no (mockito|hamcrest)"]},{"label":"Asks for only code","patterns":["only the code","no (text|explanation)","one code block"]}],"hint":"Tests are code your team maintains. Name them, give the exact cases and messages, and say which libraries are actually on the classpath.","rubricLabels":["Has exactly 3 @Test methods named parsesThousandsSeparator, rejectsMissingDollarSign and rejectsThreeDecimals","parsesThousandsSeparator asserts Money.parse(\"$1,234.50\").cents() equals 123450, and the other two use assertThrows(IllegalArgumentException.class, ...) on \"12.00\" and \"$1.999\" and check the messages \"missing $\" and \"too many decimals\"","Uses only JUnit 5 imports (org.junit.jupiter.api.Test and the static Assertions), with no AssertJ, Hamcrest, Mockito or @ParameterizedTest"]}