Welcome to our lesson on using Fakes as test doubles in Test Driven Development (TDD) with Kotlin, JUnit, and Mockito. In this lesson, you'll explore how fakes can streamline your testing by simulating real-world components. Our journey so far has exposed you to various test doubles like dummies, stubs, spies, and mocks. Now, we'll dive into fakes, which enable you to create realistic implementations that mirror complex dependencies, making your tests more robust and reliable. As always, we'll practice the TDD cycle: Red, Green, Refactor, as we see how fakes fit into our testing strategy.
Let's see how to implement a simple fake: an InMemoryUserRepository
. This serves as a stand-in for a real database repository, providing controlled behavior for our tests.
Create a class InMemoryUserRepository.kt
:
Kotlin1class InMemoryUserRepository : IUserRepository { 2 private val users = mutableMapOf<String, User>() 3 private var currentId = 1 4 5 private fun generateId(): String = currentId++.toString() 6 7 override fun create(userData: User): User { 8 val user = User() 9 user.id = generateId() 10 user.email = userData.email 11 user.name = userData.name 12 user.createdAt = LocalDateTime.now() 13 users[user.id] = user 14 return user 15 } 16 17 override fun findById(id: String): User? = users[id] 18 19 override fun findByEmail(email: String): User? { 20 return users.values.firstOrNull { it.email.equals(email, ignoreCase = true) } 21 } 22 23 override fun update(id: String, data: User): User? { 24 val existing = users[id] 25 existing ?: return null 26 27 data.email?.let { existing.email = it } 28 data.name?.let { existing.name = it } 29 users[id] = existing 30 return existing 31 } 32 33 override fun delete(id: String): Boolean = users.remove(id) != null 34 35 override fun findAll(): List<User> = users.values.toList() 36 37 override fun clear() { 38 users.clear() 39 currentId = 1 40 } 41}
Explanation:
- We create an in-memory store for users using a mutable map.
- Each function simulates typical database operations such as creating and finding users.
- The
clear
method ensures data isolation between tests, a crucial feature for repeatable outcomes.
By having a controlled data store, we make sure our tests are focused on business logic and not dependent on an external database. Fakes often mimic the behavior of real components, providing a safe and predictable testing environment without the complexity of external systems.
Next, we will use the fake repository to test a UserService
.
1 - Red: Write Failing Tests
Create a test file UserServiceTest.kt
:
Kotlin1import org.junit.jupiter.api.BeforeEach 2import org.junit.jupiter.api.Test 3import kotlin.test.assertEquals 4import kotlin.test.assertNotNull 5 6class UserServiceTest { 7 private lateinit var userService: UserService 8 private lateinit var userRepository: InMemoryUserRepository 9 10 @BeforeEach 11 fun setUp() { 12 // Arrange 13 userRepository = InMemoryUserRepository() 14 userService = UserService(userRepository) 15 } 16 17 @Test 18 fun testRegisterUserCreatesNewUserSuccessfully() { 19 // Act 20 val user = userService.registerUser("test@example.com", "Test User") 21 22 // Assert 23 assertEquals("test@example.com", user.email) 24 assertEquals("Test User", user.name) 25 assertNotNull(user.id) 26 assertNotNull(user.createdAt) 27 28 val repositoryUsers = userRepository.findAll() 29 assertEquals(1, repositoryUsers.size) 30 assertEquals("Test User", repositoryUsers[0].name) 31 } 32}
Run this test to confirm it fails, as we haven't implemented the logic yet.
2 - Green: Implement Minimal Code
Now, modify the UserService.kt
to ensure the test passes:
Kotlin1class UserService(private val repository: IUserRepository) { 2 3 fun registerUser(email: String, name: String): User { 4 return repository.create(User(email = email, name = name)) 5 } 6}
Rerun the test. It should now pass, confirming that our implementation meets the defined requirement.
In this lesson, we explored the implementation and use of fakes in TDD, specifically via an in-memory repository for user management. Remember the steps of TDD:
- Red: Write a test that fails first, setting clear goals for implementation.
- Green: Implement just enough code to make your test pass.
- Refactor: Improve code quality without altering functionality.
Leverage the practice exercises to reinforce these concepts with hands-on examples. Congratulations on navigating the complexities of testing with fakes; your commitment is paving the way for building efficient, scalable applications. This is the final lesson of the course, so kudos for reaching this milestone! Keep exploring and applying TDD principles in your projects.
