[{"content":"How to Automate Spring Boot Test Writing for Your Team Using LLMs and Custom Skills Today we will review how to automate test writing for a team working on a Spring Boot application.\nWe could ask Cursor/Windsurf/Opencode to create a test just from a prompt. From a developer\u0026rsquo;s experience, this is the fastest way — but companies usually have their own standards, specifics for writing and maintaining the codebase, and best practices/patterns.\nAnd yes, this approach works even without an internet connection. See: Part 1 — Local LLM setup. You can bootstrap your own LLM server and save developer resources.\nIn the long term, companies should consider RAG + ChromaDB. But for daily use, you can start with Skills.\nA company can have a repository with a common set of Skills. If a developer wants to extend it, they create a PR with a new or modified Skill. New team members then get company-level standards out of the box — just like formatters and code styles in previous years.\nTo manage skills we will use:\nhttps://github.com/numman-ali/openskills/\nIt allows to install custom Skills from different sources, including git repository. OpenSkill can generate Agents.md, which will already be used on the developer\u0026rsquo;s side by the AI agent.\nCharging app to test First of all we need this application, and thanks to Deepseek + Opencode + BigPickle and we already have Delivery system for Planet Express:\nhttps://github.com/alexey-yurganov/jroom36-planet-express-delivery/tree/main\nThis is common Spring App with Data layer, Service Layer and REST API layer. We will create some tests on it.\nTest Types We plan to cover application with common types of tests:\n@WebMvcTest - for REST API Controllers @DataJpaTest - for Spring repositories @Testcontainers - for integration tests Mockito\u0026amp;Junit - for services Create Skills Let\u0026rsquo;s install OpenSkills to be able manage our set of skills:\n1 2 npm i -g openskills openskills list For a first time, the response should look like:\n1 2 3 4 5 6 7 8 ❯ openskills list Available Skills: No skills installed. Install skills: npx openskills install anthropics/skills # Project (default) npx openskills install owner/skill --global # Global (advanced) Next one, let\u0026rsquo;s install common skills from anthropics\n1 2 openskills install anthropics/skills openskills sync In interactive mode you could explore/select available Skills in this repo, by default all items are selected. We are interested in one specific skill named as:\nskill-creator - it will help create our own skills in common way Because each dev could have different set of skills and from different repos this should not be part of SCM/git.\nSkills \u0026amp; Unit Test for Spring Boot: Services, Controllers, Data I use OpenCode but any agent could be used. Need to open project where skill skill-creator had been installed and ask from prompt\n1 Use skill-creator for creating skill which allow to create Unit tests for Spring Boot Services OpenCode asked for some parameters. For this round, we will create a common skill to cover possible test types but technically you could split on separate skills and support them independently.\nSkill created: spring-boot-testing Location: .claude/skills/spring-boot-testing/\n1 2 3 4 5 6 7 8 9 10 spring-boot-testing/ ├── SKILL.md — Orchestration + workflow ├── LICENSE.txt ├── .openskills.json └── references/ ├── best-practices.md — Naming, AAA, AssertJ, verification checklist ├── service-testing.md — @ExtendWith + @Mock + @InjectMocks patterns ├── repository-testing.md — @DataJpaTest + TestEntityManager ├── controller-testing.md — @WebMvcTest + MockMvc + @MockBean └── integration-testing.md — @SpringBootTest + Testcontainers Now we could commit and push skill into repo, for example:\nhttps://github.com/alexey-yurganov/spring-boot-testing-skills/tree/main/spring-boot-testing\nCreate test using spring-boot-testing skill We could install skill into our project and generate some test:\n1 2 3 4 5 6 7 8 9 10 11 12 openskills install alexey-yurganov/spring-boot-testing-skills/spring-boot-testing ❯ openskills install alexey-yurganov/spring-boot-testing-skills/spring-boot-testing Installing from: alexey-yurganov/spring-boot-testing-skills/spring-boot-testing Location: project (.claude/skills) Default install is project-local (./.claude/skills). Use --global for ~/.claude/skills. ✔ Repository cloned ✅ Installed: spring-boot-testing Location: /Users/lx2/code/jroom36-planet-express-delivery/.claude/skills/spring-boot-testing openskills sync After that you could ensure that it is done and skill is available:\n1 2 3 4 5 6 7 ❯ openskills list Available Skills: spring-boot-testing (project) Comprehensive guidance for writing unit and integration tests for Spring Boot applications. Use this skill whenever the user asks to write tests for Spring Boot components, generate test classes, mock Spring beans, set up test slices (@WebMvcTest, @DataJpaTest, @JsonTest), use Testcontainers for integration tests, or asks about Spring Boot testing best practices, JUnit 5, Mockito, or AssertJ in the context of Spring Boot. Also trigger when the user mentions covering service/business logic with tests, testing JPA repositories, testing REST controllers, or wants to add tests to an existing Spring Boot project — even if they don\u0026#39;t explicitly say \u0026#34;Spring Boot\u0026#34; (look for Maven/Gradle projects with spring-boot-starter dependencies). Do NOT trigger for non-Spring Java testing (plain JUnit without Spring), frontend testing, or end-to-end testing with tools like Selenium or Cypress unless Spring Boot is the backend under test. Summary: 1 project, 0 global (1 total) Time to create some test. For example we have Service\nhttps://github.com/alexey-yurganov/jroom36-planet-express-delivery/blob/main/src/main/java/com/planetexpress/service/CrewAssignmentService.java\nlet\u0026rsquo;s ask OpenCode to create test for this, type this into the prompt of your Agent (OpenCode in my case)\n1 Using skill spring-boot-testing create test for CrewAssignmentService The test was created. If you run the command to execute all tests, you can verify that they pass successfully:\n1 make test Generated test could be reviewed here:\nhttps://github.com/alexey-yurganov/jroom36-planet-express-delivery/blob/main/src/test/java/com/planetexpress/service/CrewAssignmentServiceTest.java\nLets see code-coverage (85%):\nObservations and Limitations Yes, there are some hardcoded strings — but this is due to the original codebase we used for testing. Yes, some scenarios could be added to handle all branches and improve code coverage. Yes, it could use a logger via @Log. There are other parts that could be enhanced based on your team\u0026rsquo;s rules. Next time, the generated tests will be free of these issues.\nHomework: try to generate tests for Repositories and Integration tests, it is not committed and should not force conflicts.\nSummary In this article, we showed how a team can systematically automate Spring Boot test writing using AI and OpenSkills.\nKey points Plain prompts are fast but inconsistent across developers. Custom Skills encode your company\u0026rsquo;s best practices and apply them automatically. OpenSkills lets you manage Skills as code — install, sync, review via PRs. No internet? No problem. You can use local LLMs (see Part 1). Real example: We generated a test for CrewAssignmentService with 85% code coverage — and immediately identified areas for improvement (hardcoded strings, logging, missing branches). Homework from the article Generate tests for Repositories and Integration tests Adapt generated tests to your team\u0026rsquo;s internal standards Ensure no conflicts with existing code Final thought A custom Skill is not just a prompt template. It is:\n✅ An executable coding standard — rules applied automatically, not just documented ✅ A knowledge distribution system — update once, everyone gets the new standard ✅ An integration hub — can call CI/CD pipelines, SonarQube, Jira, or internal APIs This approach lowers the onboarding barrier for new developers, speeds up code reviews, and makes tests predictable and maintainable.\n","permalink":"https://jroom36.github.io/techs/local-ai-java-testing/part2-devoxx-skills-spring-boot-tests/","summary":"\u003ch2 id=\"how-to-automate-spring-boot-test-writing-for-your-team-using-llms-and-custom-skills\"\u003eHow to Automate Spring Boot Test Writing for Your Team Using LLMs and Custom Skills\u003c/h2\u003e\n\u003cp\u003eToday we will review how to automate test writing for a team working on a Spring Boot application.\u003c/p\u003e\n\u003cp\u003eWe could ask Cursor/Windsurf/Opencode to create a test just from a prompt. From a developer\u0026rsquo;s experience, this is the fastest way — but companies usually have their own standards, specifics for writing and maintaining the codebase, and best practices/patterns.\u003c/p\u003e","title":"Part 2: Supercharging Spring Boot Development with OpenSkills — Practical Examples for Controllers, Services, and Repositories"},{"content":"Maintaining high code coverage is essential but often tedious — especially when you need to cover controllers, services, repositories, and edge cases individually. AI‑powered test generation can automate most of this work, cutting hours of manual effort (in some cases, up to 40–50% of testing time).\nHowever, many enterprises — especially in finance, healthcare, or regulated industries — are not willing to share their codebase with third‑party LLM providers like OpenAI or Anthropic. Security policies, intellectual property concerns, and compliance requirements (e.g., GDPR, SOC2) often mandate that no data leaves the corporate network.\nThis has led to a clear industry trend: running local LLMs in‑house with full air‑gapped isolation. Teams are deploying models like Google\u0026rsquo;s Gemma 4b via Ollama, integrating them directly into IntelliJ IDEA using plugins such as Devoxx Genie, and keeping all code and inferences entirely within their own infrastructure — with no internet required and no recurring API costs.\nIn this three‑part series, I\u0026rsquo;ll show you exactly how to set this up for a Spring Boot application — from local LLM setup to enterprise‑grade Docker isolation.\nInstall Ollama + LLMs My working machine is a MacBook, so we could use brew:\n1 2 brew uninstall ollama brew install --cask ollama A reinstall ensures that the latest version will be installed, which is compatible with latest format of LLM models\nLocal LLMs for Java Development Model Best For Context Window (Tokens) Recommended RAM (Q4) Key Strength / Why it fits Java Qwen 2.5 Coder (7B) Code Generation \u0026amp; Logic 128K ~5.5 GB Top HumanEval score (76.0). Excellent at Java streams/logic. Gemma 4 (e4b / 9B) General Java / Balanced 128K ~6–10 GB Low verbosity. Generates clean, standard Java code. DeepSeek Coder V2 (16B) Complex Refactoring 128K ~10–12 GB High accuracy (83.5% HumanEval). Best for test generation. Mistral Small 3 (7B) Real-time Autocomplete 32K ~5.5 GB Very fast (~50 t/s). Ideal for inline suggestions. Phi-4-mini (3.8B) Low-resource machines 16K ~3.5 GB Very low RAM usage. Runs on 8GB laptops. Let\u0026rsquo;s pull a couple of models\n1 2 # start ollama ollama serve 1 2 ollama pull qwen2.5-coder:7b ollama pull gemma4:e4b You could skip qwen2.5-coder:7b - but for my setup it showed more stable results in terms of memory consumption, so you could play with this one as well.\nDetailed overview on Gemma4:e4b:\nhttps://huggingface.co/google/gemma-4-E4B https://ollama.com/library/gemma4:e4b After pulling you could try\n1 ollama list and results should be like this\n1 2 3 4 ❯ ollama list NAME ID SIZE MODIFIED gemma4:e4b c6eb396dbd59 9.6 GB 26 minutes ago qwen2.5-coder:7b dae161e27b0e 4.7 GB 3 hours ago For hello world ping just type:\n1 ollama run qwen2.5-coder:7b \u0026#34;Hello\u0026#34; and response should be like\n1 Hello! How can I assist you today? Near the same for gemma4:e4b\n1 ollama run gemma4:e4b \u0026#34;Hello\u0026#34; Required more time and after Thinking\u0026hellip; it should print near the same:\n1 Hello! How can I help you today? 😊 Devoxx Genie: Connect IntelliJ IDEA to Gemma4:e4b There are some alternatives on marketplace to play with local LLM. You\u0026rsquo;ll also hear about Continue.dev and LM Studio.\nContinue.dev works across VS Code and IntelliJ, supports local Ollama models, and does RAG. But for Java‑specific workflows (like generating @SpringBootTest or @DataJpaTest with all the right context), Devoxx Genie offers more polished features — including agent mode and spec‑driven development.\nLM Studio is a standalone desktop app that runs models locally and exposes an OpenAI‑compatible API — any plugin can talk to it. The trade‑off is shallow IDE integration. You manage prompts and context yourself.\nRolling your own with Ollama\u0026rsquo;s raw API is always possible — but then you\u0026rsquo;re building prompt management, context handling, and result parsing from scratch.\nFor IntelliJ + Spring Boot, Devoxx Genie is a practical choice.\nIt works offline It works with IntelliJ IDEA Community Edition It supports RAG - you could train LLMs on your project and persist resulting vectors in ChromaDB and it could act as tech expert of your App It supports SKILLs - various, includes own location, Claude, Agents. No need to adapt your existing codebase in most cases More details on official plugin page: https://plugins.jetbrains.com/plugin/24169-devoxxgenie\nAfter installing from market place, you could open plugin settings and ensure that it points correctly on your Ollama setup:\nBy default Ollama will start on http://localhost:11434/\nAdditionally you could Enable Stream Mode to be able see dynamic results\nGenerate Unit Test with LLM Let\u0026rsquo;s start with some snippet to test\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class BinarySearch { public static int binarySearch(int[] array, int target) { if (array == null || array.length == 0) { return -1; } int left = 0; int right = array.length - 1; while (left \u0026lt;= right) { int mid = left + (right - left) / 2; if (array[mid] == target) { return mid; } else if (array[mid] \u0026lt; target) { left = mid + 1; } else { right = mid - 1; } } return -1; } } Let\u0026rsquo;s Open DevoxxGenie plugin in IDEA. We could observe that installed LLMs were discovered and we could select gemma4:e4b or qwen2.5-coder:7b\nOpen java class with BinarySearch and type in chat to ask for test generation:\n1 /test qwen2.5-coder:7b responded by 14.5s with:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.github.jroom36.search; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class BinarySearchTest { @Test public void testBinarySearchFound() { int[] array = {1, 2, 3, 4, 5}; int target = 3; int expectedIndex = 2; assertEquals(expectedIndex, BinarySearch.binarySearch(array, target)); } @Test public void testBinarySearchNotFound() { int[] array = {1, 2, 3, 4, 5}; int target = 6; int expectedResult = -1; assertEquals(expectedResult, BinarySearch.binarySearch(array, target)); } @Test public void testBinarySearchEmptyArray() { int[] array = {}; int target = 1; int expectedResult = -1; assertEquals(expectedResult, BinarySearch.binarySearch(array, target)); } @Test public void testBinarySearchNullArray() { int[] array = null; int target = 1; int expectedResult = -1; assertEquals(expectedResult, BinarySearch.binarySearch(array, target)); } } gemma4:e4b responded by 50s with more complex test:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 package com.github.jroom36.search; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class BinarySearchTest { @Test void testBinarySearchTargetFoundInMiddle() { // Array: 1, 2, 3, 4, 5, Target: 3 (Index 2) int[] array = {1, 2, 3, 4, 5}; int target = 3; assertEquals(2, BinarySearch.binarySearch(array, target), \u0026#34;Should find the element in the middle.\u0026#34;); } @Test void testBinarySearchTargetFoundAtStart() { // Array: 1, 2, 3, 4, Target: 1 (Index 0) int[] array = {1, 2, 3, 4}; int target = 1; assertEquals(0, BinarySearch.binarySearch(array, target), \u0026#34;Should find the element at the start.\u0026#34;); } @Test void testBinarySearchTargetFoundAtEnd() { // Array: 1, 2, 3, 4, Target: 4 (Index 3) int[] array = {1, 2, 3, 4}; int target = 4; assertEquals(3, BinarySearch.binarySearch(array, target), \u0026#34;Should find the element at the end.\u0026#34;); } @Test void testBinarySearchTargetNotFoundSmallerThanAll() { // Array: 5, 10, 15, Target: 1 (Index -1) int[] array = {5, 10, 15}; int target = 1; assertEquals(-1, BinarySearch.binarySearch(array, target), \u0026#34;Should return -1 when target is smaller than all elements.\u0026#34;); } @Test void testBinarySearchTargetNotFoundLargerThanAll() { // Array: 5, 10, 15, Target: 20 (Index -1) int[] array = {5, 10, 15}; int target = 20; assertEquals(-1, BinarySearch.binarySearch(array, target), \u0026#34;Should return -1 when target is larger than all elements.\u0026#34;); } @Test void testBinarySearchTargetNotFoundInBetween() { // Array: 1, 3, 5, Target: 4 (Index -1) int[] array = {1, 3, 5}; int target = 4; assertEquals(-1, BinarySearch.binarySearch(array, target), \u0026#34;Should return -1 when target is not present.\u0026#34;); } @Test void testBinarySearchEmptyArray() { // Edge case: Empty array int[] array = {}; int target = 5; assertEquals(-1, BinarySearch.binarySearch(array, target), \u0026#34;Should return -1 for an empty array.\u0026#34;); } @Test void testBinarySearchNullArray() { // Edge case: Null array int[] array = null; int target = 5; assertEquals(-1, BinarySearch.binarySearch(array, target), \u0026#34;Should return -1 for a null array.\u0026#34;); } @Test void testBinarySearchSingleElementFound() { // Edge case: Single element array, found int[] array = {7}; int target = 7; assertEquals(0, BinarySearch.binarySearch(array, target), \u0026#34;Should find the element in a single-element array.\u0026#34;); } @Test void testBinarySearchSingleElementNotFound() { // Edge case: Single element array, not found int[] array = {7}; int target = 8; assertEquals(-1, BinarySearch.binarySearch(array, target), \u0026#34;Should return -1 for a single-element array when target is missing.\u0026#34;); } } Also if enable Agent mode in DevoxxGenie - gemma4:e4b could apply changes on file system and could run just created tests.\nThis was \u0026lsquo;hello world\u0026rsquo; example that showed how we could use local LLM to generate unit tests for Java application. On real project you most probably will use https://github.com/numman-ali/openskills/ to manage skills. Integrate RAG and ChromaDB to train LLM on exactly approaches used in your project, not abstract testing. And will create separate skills to be able handle different kinds of tests in your enterprise, like:\nUnit tests with JUnit5 and Mockito @SpringBootTest @DataJpaTest with @Testcontainers @WebMvcTest In next part we will check how to Isolate LLM with docker and ensure that no sensitive data will be transferred from your project.\n","permalink":"https://jroom36.github.io/techs/local-ai-java-testing/part1-ollama-gemma-devoxx-setup/","summary":"\u003cp\u003eMaintaining high code coverage is essential but often tedious — especially when you need to cover controllers, services, repositories, and edge cases individually. AI‑powered test generation can automate most of this work, cutting hours of manual effort (in some cases, up to 40–50% of testing time).\u003c/p\u003e\n\u003cp\u003eHowever, many enterprises — especially in finance, healthcare, or regulated industries — are not willing to share their codebase with third‑party LLM providers like OpenAI or Anthropic. Security policies, intellectual property concerns, and compliance requirements (e.g., GDPR, SOC2) often mandate that no data leaves the corporate network.\u003c/p\u003e","title":"Part 1: Running Local LLM for Java Tests — Ollama + Gemma 4b + Devoxx Genie"}]