{"id":1447,"date":"2023-05-14T17:31:19","date_gmt":"2023-05-15T00:31:19","guid":{"rendered":"https:\/\/bullyrooks.com\/?p=1447"},"modified":"2023-05-14T17:31:22","modified_gmt":"2023-05-15T00:31:22","slug":"unit-tests-vs-component-tests-which-ones-better-for-software-quality","status":"publish","type":"post","link":"https:\/\/bullyrooks.com\/index.php\/2023\/05\/14\/unit-tests-vs-component-tests-which-ones-better-for-software-quality\/","title":{"rendered":"Unit Tests vs. Component Tests: Which One&#8217;s Better for Software Quality?"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>If you&#8217;re considering software testing and are torn between unit tests and component tests, this article is for you. We will present the advantages and disadvantages of each testing method, helping you select the right one for your project. Additionally, we&#8217;ll discuss how combining both techniques can lead to improved software quality.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Our Code Example<\/h2>\n\n\n\n<p>Here&#8217;s a very simple 3 layer spring REST endpoint example that has a POST endpoint to create a book and a GET endpoint to retrieve a book<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Controller\n@RestController\npublic class BookController {\n    @Autowired\n    private BookService bookService;\n\n    @GetMapping(\"\/book\/{id}\")\n    public ResponseEntity&lt;Book> getBookById(@PathVariable Long id) {\n        Book book = bookService.findById(id);\n        return ResponseEntity.ok(book);\n    }\n\n    @PostMapping(\"\/book\")\n    public ResponseEntity&lt;Book> createBook(@RequestBody Book book) {\n        Book savedBook = bookService.createBook(book);\n        return ResponseEntity.ok(savedBook);\n    }\n}\n\n\/\/ Service\n@Service\npublic class BookService {\n    @Autowired\n    private BookRepository bookRepository;\n\n    public Book findById(Long id) {\n        return bookRepository.findById(id).orElseThrow(() -> new RuntimeException(\"Book not found\"));\n    }\n\n    public Book createBook(Book book) {\n        return bookRepository.save(book);\n    }\n}\n\n\/\/ Repository\npublic interface BookRepository extends JpaRepository&lt;Book, Long> {\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">The Drawbacks of Unit Tests<\/h2>\n\n\n\n<p>Unit tests focus on individual components or functions within a codebase. They\u2019re vital for ensuring your code is up to par, but they come with some challenges:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Too much focus on the nitty-gritty: Unit tests can be fragile since they&#8217;re all about specific implementation details. They might break when you change the codebase.<\/li>\n\n\n\n<li>A lot to keep up with: When you&#8217;ve got a ton of unit tests, keeping them up to date can be a real headache.<\/li>\n\n\n\n<li>Not enough integration coverage: Unit tests might not cover how components work together, so you could miss some integration problems.<\/li>\n\n\n\n<li>False confidence: A bunch of passing unit tests could make you feel good about your code quality, even if you haven&#8217;t covered all the bases.<\/li>\n\n\n\n<li>Enforcing the design: Isolating each class via mocks creates a hardened interface between each service.  If you aim to modify your design, you\u2019ll find that you\u2019ll have to change not only multiple classes but also multiple tests.<\/li>\n<\/ol>\n\n\n\n<p>Here&#8217;s what the unit tests covering these endpoints would look like, using mocks to isolate each layer:<\/p>\n\n\n\n<p>Controller:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@RunWith(SpringRunner.class)\n@WebMvcTest(BookController.class)\npublic class BookControllerTest {\n\n    @Autowired\n    private MockMvc mockMvc;\n\n    @MockBean\n    private BookService bookService;\n\n    @Test\n    public void getBookByIdTest() throws Exception {\n        Book book = new Book(1L, \"Test Title\", \"Test Author\");\n        when(bookService.findById(1L)).thenReturn(book);\n\n        mockMvc.perform(get(\"\/book\/1\"))\n                .andExpect(status().isOk())\n                .andExpect(jsonPath(\"$.title\", is(book.getTitle())));\n    }\n\n    @Test\n    public void createBookTest() throws Exception {\n        Book book = new Book(1L, \"Test Title\", \"Test Author\");\n        when(bookService.createBook(any(Book.class))).thenReturn(book);\n\n        mockMvc.perform(post(\"\/book\")\n                        .contentType(MediaType.APPLICATION_JSON)\n                        .content(new ObjectMapper().writeValueAsString(book)))\n                .andExpect(status().isOk())\n                .andExpect(jsonPath(\"$.title\", is(book.getTitle())));\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>Service:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@RunWith(SpringRunner.class)\npublic class BookServiceTest {\n\n    @InjectMocks\n    private BookService bookService;\n\n    @Mock\n    private BookRepository bookRepository;\n\n    @Test\n    public void findByIdTest() {\n        Book book = new Book(1L, \"Test Title\", \"Test Author\");\n        when(bookRepository.findById(1L)).thenReturn(Optional.of(book));\n\n        Book foundBook = bookService.findById(1L);\n\n        assertEquals(book.getTitle(), foundBook.getTitle());\n    }\n\n    @Test\n    public void createBookTest() {\n        Book book = new Book(1L, \"Test Title\", \"Test Author\");\n        when(bookRepository.save(any(Book.class))).thenReturn(book);\n\n        Book savedBook = bookService.createBook(book);\n\n        assertEquals(book.getTitle(), savedBook.getTitle());\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>Repository:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@RunWith(SpringRunner.class)\n@DataJpaTest\npublic class BookRepositoryTest {\n\n    @Autowired\n    private TestEntityManager entityManager;\n\n    @Autowired\n    private BookRepository bookRepository;\n\n    @Test\n    public void findByIdTest() {\n        Book book = new Book(1L, \"Test Title\", \"Test Author\");\n        entityManager.persistAndFlush(book);\n\n        Optional&lt;Book> foundBook = bookRepository.findById(1L);\n\n        assertTrue(foundBook.isPresent());\n        assertEquals(\"Test Title\", foundBook.get().getTitle());\n    }\n\n    @Test\n    public void saveBookTest() {\n        Book book = new Book(1L, \"Test Title\", \"Test\n<\/code><\/pre>\n\n\n\n<p>As you can see, this is a lot of test code.  This code has to be maintained just the same as the functional code.  Additionally, the use of mocks hardens the interfaces between classes, making changes between classes difficult because of the cascading changes to the tests that will result.  This <em>might<\/em> be desired in a mature system, but the reduction in flexibility is usually not desired.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The  Advantages of Component Tests<\/h2>\n\n\n\n<p>Component tests (also called integration tests) look at groups of components or the whole system. They&#8217;ve got some advantages over unit tests:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Real-life scenarios: Component tests show you how the system works in the real world, so you get a better idea of how components interact and what the overall behavior is.<\/li>\n\n\n\n<li>Better integration coverage: By testing components together, you can find issues that unit tests might miss, like data flow, communication, or dependency management problems.<\/li>\n\n\n\n<li>Less brittleness: Since they focus on the big picture instead of tiny details, component tests are less likely to break when you change the code.<\/li>\n\n\n\n<li>Efficient testing: Sometimes, component tests can give you better test coverage with fewer tests. That means less maintenance and an easier time finding problems.<\/li>\n<\/ol>\n\n\n\n<p>Here&#8217;s example tests that should cover the same functionality as the unit tests:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\nclass BookControllerComponentTest {\n\n    @Autowired\n    private WebTestClient webTestClient;\n\n    @Autowired\n    private BookRepository bookRepository;\n\n    @AfterEach\n    public void tearDown() {\n        bookRepository.deleteAll();\n    }\n\n    @Test\n    void getBookByIdTest() {\n        Book book = new Book(1L, \"Test Title\", \"Test Author\");\n        bookRepository.save(book);\n\n        webTestClient.get()\n                .uri(\"\/book\/{id}\", book.getId())\n                .exchange()\n                .expectStatus().isOk()\n                .expectBody()\n                .jsonPath(\"$.title\").isEqualTo(book.getTitle())\n                .jsonPath(\"$.author\").isEqualTo(book.getAuthor());\n    }\n\n    @Test\n    void createBookTest() {\n        Book book = new Book(1L, \"Test Title\", \"Test Author\");\n\n        webTestClient.post()\n                .uri(\"\/book\")\n                .contentType(MediaType.APPLICATION_JSON)\n                .bodyValue(book)\n                .exchange()\n                .expectStatus().isOk()\n                .expectBody()\n                .jsonPath(\"$.title\").isEqualTo(\"Test Title\")\n                .jsonPath(\"$.author\").isEqualTo(\"Test Author\");\n\n        \/\/ Fetch the book from the database\n        List&lt;Book> books = bookRepository.findByTitle(\"Test Title\");\n\n        \/\/ Validate that the book was correctly saved\n        Assertions.assertFalse(books.isEmpty(), \"No books found\");\n        Assertions.assertEquals(book.getTitle(), books.get(0).getTitle());\n        Assertions.assertEquals(book.getAuthor(), books.get(0).getAuthor());\n    }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>In this case, the @SpringBootTest annotation instructs Spring Boot to launch your application, including creating the ApplicationContext and starting the server.<\/p>\n\n\n\n<p>We&#8217;re using <code>WebTestClient<\/code> instead of <code>MockMvc<\/code> to perform HTTP requests, and we&#8217;re using <code>@Autowired<\/code> to get an instance of our <code>BookRepository<\/code> to setup and tear down our data for each test.<\/p>\n\n\n\n<p>Please note that these component tests will run slower compared to unit tests as they are loading the entire application context and not just a slice of it. Also, they would need an actual database connection to run, as opposed to the mocked repository in unit tests.<\/p>\n\n\n\n<p>Personally, I use H2 as a quick and dirty in memory database for testing.  Here&#8217;s how you could set it up for this example<br><\/p>\n\n\n\n<p>First, add H2 dependency in your <code>pom.xml<\/code> file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>&lt;dependency>\n    &lt;groupId>com.h2database&lt;\/groupId>\n    &lt;artifactId>h2&lt;\/artifactId>\n    &lt;scope>test&lt;\/scope>\n&lt;\/dependency><\/code><\/pre>\n\n\n\n<p>Then, you need to create a <code>application-test.properties<\/code> file in your <code>src\/test\/resources<\/code> directory. This is where you&#8217;ll specify the configurations for your H2 database. Here&#8217;s what that file might look like:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE\nspring.datasource.driverClassName=org.h2.Driver\nspring.datasource.username=sa\nspring.datasource.password=\nspring.jpa.database-platform=org.hibernate.dialect.H2Dialect\nspring.jpa.hibernate.ddl-auto=create-drop\nspring.h2.console.enabled=true<\/code><\/pre>\n\n\n\n<p>In this file, we&#8217;re configuring Spring Boot to use an in-memory H2 database for the data source and Hibernate to automatically create and drop the database schema.<\/p>\n\n\n\n<p>Finally, you need to tell Spring Boot to use this profile during testing. You can do this with the <code>@ActiveProfiles<\/code> annotation in your test class:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\n@ActiveProfiles(\"test\")\nclass BookControllerComponentTest {\n    \/\/ ...\n}<\/code><\/pre>\n\n\n\n<p>Yes, there&#8217;s a little bit more configuration (for the test environment), but the test code is <strong>extremely<\/strong> small.  Additionally, the test code tests the interface of our service only (the REST endpoints) which tests the behavior we care about with a high level of coverage.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Striking the Perfect Balance for Software Quality<\/h2>\n\n\n\n<p>The best approach to software testing involves using both unit tests and component tests. Unit tests are great for testing individual functions or components in isolation, while component tests make sure that the system works as expected when all its parts are put together. By combining these testing strategies, developers can achieve a more comprehensive and reliable assessment of their software&#8217;s quality.<\/p>\n\n\n\n<p>For our Spring Boot example, we can further enhance the application by adding more features like update and delete book. To maintain software quality, we would create both unit tests and component tests for each new feature. This way, we can ensure that the individual components work correctly in isolation and that the system as a whole functions as expected.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Understanding the strengths and weaknesses of unit tests and component tests is crucial for any software development project. By carefully considering the benefits and drawbacks of each approach and using a blend of both testing strategies, developers can create software with enhanced quality, functionality, and reliability.<\/p>\n\n\n\n<p>With a solid understanding of both testing methodologies, developers can make informed decisions and create robust, reliable software. Remember, the key is to strike a balance between the two testing approaches to get the best of both worlds for top-notch code quality.<\/p>\n\n\n\n<p>This is my ultimate recommendation:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Rely on component tests heavily, you can use them as your TDD starting point if you want (and if you use TDD).<\/li>\n\n\n\n<li>Use unit tests to cover individual cases with multiple branches, clauses, or complicated calculations. Whenever possible, utilize parameterized tests to cover those multiple cases.<\/li>\n\n\n\n<li>Also, apply unit tests to areas of the code that are extremely important to mitigate the risk of breaking the system. You may want a combination of both unit and component tests in these critical areas.<\/li>\n<\/ul>\n\n\n\n<p>In summary, understanding and applying both unit tests and component tests in software development can significantly improve the quality and reliability of the end product. Unit tests allow developers to verify individual components in isolation, while component tests assess the integrated functionality of these components. Striking a balance between these two testing methodologies, rather than relying solely on one, can help developers to address a wider range of potential issues and ensure a more robust and reliable software system.<\/p>\n\n\n\n<p>Remember, the primary goal is to create software that not only meets the desired functionality but is also easy to maintain, understand, and enhance. By employing a balanced testing strategy that includes both unit tests and component tests, you can achieve this goal more effectively. After all, it&#8217;s not just about having a bug-free code; it&#8217;s about building a system that stands the test of time and is resilient in the face of future changes.<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"entry-summary\">\nDive into the good and bad of unit tests and component tests in software development. Find out how to get the best of both worlds for top-notch code quality.\n<\/div>\n<div class=\"link-more\"><a href=\"https:\/\/bullyrooks.com\/index.php\/2023\/05\/14\/unit-tests-vs-component-tests-which-ones-better-for-software-quality\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &ldquo;Unit Tests vs. Component Tests: Which One&#8217;s Better for Software Quality?&rdquo;<\/span>&hellip;<\/a><\/div>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[41],"tags":[202,150],"course":[],"class_list":["post-1447","post","type-post","status-publish","format-standard","hentry","category-software-development","tag-automated-testing","tag-testing","entry"],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":1402,"url":"https:\/\/bullyrooks.com\/index.php\/2022\/03\/08\/kube-cloud-pt6-fulfill-the-consumer-contract-test-for-rest-endpoint\/","url_meta":{"origin":1447,"position":0},"title":"Kube Cloud Pt6 | Fulfill the Consumer Contract Test for REST Endpoint","author":"Bullyrook","date":"March 8, 2022","format":false,"excerpt":"Now we need to fulfill our contract with the consumer. We just need to add a few pieces that that a consumer build will trigger a provider verification. Add a Verification Workflow Go back to your branch in message-generator and add a new github action workflow called verify-changed-pact.yaml with this\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-15.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-15.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/03\/image-15.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":828,"url":"https:\/\/bullyrooks.com\/index.php\/2020\/03\/30\/simple-spring-boot-service-to-kubernetes-application-step-4-dba10da3d834\/","url_meta":{"origin":1447,"position":1},"title":"Create a REST Controller","author":"Bullyrook","date":"March 30, 2020","format":false,"excerpt":"In this article we\u2019re going to take the service layer we created in the last step and add a REST interface to it. We\u2019re going to follow the hexagonal design principles we previously discussed and add a functional or component test and explain how those test work and why they\u2019re\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1374,"url":"https:\/\/bullyrooks.com\/index.php\/2022\/03\/08\/kube-cloud-pt6-contract-testing\/","url_meta":{"origin":1447,"position":2},"title":"Kube Cloud Pt6 | Contract Testing","author":"Bullyrook","date":"March 8, 2022","format":false,"excerpt":"Contract testing may be the most misunderstood and overlooked concept in distributed software development. As we're developing microservices we're going to need to do some service governance to make sure that service versions are compatible with each other. This can normally be managed with a conversation between teams developing in\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":835,"url":"https:\/\/bullyrooks.com\/index.php\/2020\/03\/30\/simple-spring-boot-service-to-kubernetes-application-step-6-c52f5247ba20\/","url_meta":{"origin":1447,"position":3},"title":"Documentation and Code Coverage","author":"Bullyrook","date":"March 30, 2020","format":false,"excerpt":"Before we move on to getting our service ready for deployment we need to do a little more housecleaning. Sometimes these tasks get de-prioritized so we might as well take care of it now, before we move on. Endpoint Documentation Using OpenAPI is a powerful tool for documenting our REST\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1153,"url":"https:\/\/bullyrooks.com\/index.php\/2022\/01\/02\/cloud-kube-simple-rest-endpoint-and-test\/","url_meta":{"origin":1447,"position":4},"title":"Cloud Kube | Simple REST Endpoint and Test","author":"Bullyrook","date":"January 2, 2022","format":false,"excerpt":"In the previous article we created the skeleton repo and project. In this article we'll be building a very basic endpoint and component test. Although I do hate hello world examples my intent is to get us to a point where we can focus on the build and deploy path.\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/bullyrooks.com\/wp-content\/uploads\/2022\/01\/image-11.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":1229,"url":"https:\/\/bullyrooks.com\/index.php\/2022\/01\/23\/kube-cloud-pt2-automated-testing-with-testcontainers\/","url_meta":{"origin":1447,"position":5},"title":"Kube Cloud Pt2 | Automated Testing with TestContainers","author":"Bullyrook","date":"January 23, 2022","format":false,"excerpt":"In the last section we implemented service endpoint that stored data in a mongodb backend. In this session we're going to build a component test to automatically verify that functionality. TestContainer Overview TestContainers allow us to mock external resources with a docker based implementation. This is similar to \"regular\" mocking\u2026","rel":"","context":"In &quot;Software Development&quot;","block_context":{"text":"Software Development","link":"https:\/\/bullyrooks.com\/index.php\/category\/software-development\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/1447","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/comments?post=1447"}],"version-history":[{"count":2,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/1447\/revisions"}],"predecessor-version":[{"id":1449,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/1447\/revisions\/1449"}],"wp:attachment":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/media?parent=1447"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/categories?post=1447"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/tags?post=1447"},{"taxonomy":"course","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/course?post=1447"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}