As a startup studio, we spend a lot of time cultivating engineering chops that allow us to test smarter. It’s crucial for us to accelerate our client’s deployment efforts. On one hand, test cases add an incredible amount of value to the overall quality of application. On the other, they are tough and can take up a considerable amount of time. However, with the right mechanics and systems in place, they can definitely get easier, even for beginners.
1. By getting better code coverage.
2. By creating efficiencies around how long it takes you to write the tests.
3. By improving the overall test strategy
Some of the most common excuses you might hear for not having a unit test for Spring Boot applications (or for any application) is: “We didn’t have the time to write the tests due to too many changes, not enough time, tight deadlines etc. etc." We say: "No excuses. Test cases are crucial and we’re going to write testable code from day one."
But in order to do that, it’s important to learn when to:
1. Mock
2. Inject
3. Use actual objects
And equally important to understand when to:
1. Run simple JUnit test
2. Run Spring Boot test
3. Run data Java Persistence API (JPA) test
Simple example from with favorite holiday dessert
Consider this simple scenario. A cookie manufacturer makes cookies of the same weight and ships a certain quantity to the dealers. What is the easiest way to count the number of cookies sent to the dealer?
Each cookie weighs x ounces,
Then total cookies in each box will be = box weight / x
Total cookies shipped = shipment weight / x
Total boxes in each shipment = shipment weight / box weight
But, and this one is a big but, this calculation holds true only if, each cookie is the exact same weight. So, as long as the manufacturer makes sure that their testing unit weighs the cookie accurately, they will be able to send out the right number of cookies. They might not even need dedicated cookie counting systems.
The same principle applies to testing of software and code. Once we know what the smallest units of our program are, building out the tests and mocking up smaller units gets easier.
Covering some basic rules we follow while creating test cases.
1. Simple helper components from Software Development Kits (SDKs): We don’t need dedicated tests. We cover them in other classes.
2. Custom written simple helper components: Dedicated tests are required. Cover them in other classes, too, without mocking them up.
3. Complex and heavy-weight components: Write dedicated tests and then mock the dedicated tests in other classes for complex components of the product build.
4. DAO repositories: Dedicated tests are not required in the case of simple queries. But data Java Persistence API (JPA) tests are essential when using complex queries. This makes sure that the end result doesn’t change unexpectedly.
5. Services: We use dedicated tests for each service and then mock them up in other services. Services may need a combination of spring test and JUnit tests, based on the service. A more detailed look at this is coming up soon.
Now, that we know the breakdown of when to mock and when to use actuals, let me walk you through the testing strategy required for different layers in detail
1. Date Formatter
2. String Manipulation
1. Components that do heavy calculations
2. Components that make API Calls to other services or establish DB Connections
Step 1 is to understand exactly what the code is supposed to do. At the end of the day, we write tests not to please our managers or get a good SonarQube report, but to ensure that the code works as intended.
Example: Login user function test in auth service
1. New users should be able to login with a valid username and password
2. If the password is not strong, then don’t allow login
3. If the username is taken then, don’t allow login
4. User name does not contain “weird” characters
5. If other credentials like phone or email are already present, then respond with “user already exists”
Positive testing - Make sure the listed features are working as expected.
Negative testing - Most of the time we figure out negatives while writing down features like an existing username or bad password. But there can be more. Like password length, username length, limited special characters, etc.
Null testing - What if username or password or other required details are null or empty?
Exception testing - Is your code throwing exceptions where required and handling it in other cases?
Mock: Make sure your lower order components are tested correctly before mocking. For example, establishing database connections is a costly operation to build out a test, even within a memory database. Thus, it makes sense to mock it up first, especially when it can be easily done. This gives respective stakeholders and developers a chance to to test it too.
Actual: The actual build can be used for constantly changing components or services which are lightweight, such as the date validator, string formatter, etc. For service, most dependencies in a service can be mocked, including other services. However, if the result of one lightweight service is directly dependent on another service, then we prefer using the actual object instead of a mockup. How to determine that? It comes with years and years of practice and there is, unfortunately, no golden rule.
A thorough understanding of the underlying code, however, goes a long way to aid writing good tests.
But let’s go back to one of the first ‘excuses/objections’ you hear, “The code keeps changing rapidly, how do you approach that?”
1. We write the code for the most frequently changing service or component. Never mock it, actually write it. That way, we don’t miss making changes to the codes that are dependent on this constantly changing code, minimizing risk of breaking the code and any last minute surprises.
2. We use actual tests wherever we can. It sounds tedious in the beginning, but as the application starts taking shape, we drastically reduce room for errors, even when adding new features.
3. We use dedicated tests for each service and then mock them up in other services. Services may need a combination of spring test and JUnit tests, based on the service. A more detailed look at this is coming up soon.
Here is the link to the sample application from the Zemoso author.
A version of this blog post was earlier published on Medium.
As a startup studio, we spend a lot of time cultivating engineering chops that allow us to test smarter. It’s crucial for us to accelerate our client’s deployment efforts. On one hand, test cases add an incredible amount of value to the overall quality of application. On the other, they are tough and can take up a considerable amount of time. However, with the right mechanics and systems in place, they can definitely get easier, even for beginners.
1. By getting better code coverage.
2. By creating efficiencies around how long it takes you to write the tests.
3. By improving the overall test strategy
Some of the most common excuses you might hear for not having a unit test for Spring Boot applications (or for any application) is: “We didn’t have the time to write the tests due to too many changes, not enough time, tight deadlines etc. etc." We say: "No excuses. Test cases are crucial and we’re going to write testable code from day one."
But in order to do that, it’s important to learn when to:
1. Mock
2. Inject
3. Use actual objects
And equally important to understand when to:
1. Run simple JUnit test
2. Run Spring Boot test
3. Run data Java Persistence API (JPA) test
Simple example from with favorite holiday dessert
Consider this simple scenario. A cookie manufacturer makes cookies of the same weight and ships a certain quantity to the dealers. What is the easiest way to count the number of cookies sent to the dealer?
Each cookie weighs x ounces,
Then total cookies in each box will be = box weight / x
Total cookies shipped = shipment weight / x
Total boxes in each shipment = shipment weight / box weight
But, and this one is a big but, this calculation holds true only if, each cookie is the exact same weight. So, as long as the manufacturer makes sure that their testing unit weighs the cookie accurately, they will be able to send out the right number of cookies. They might not even need dedicated cookie counting systems.
The same principle applies to testing of software and code. Once we know what the smallest units of our program are, building out the tests and mocking up smaller units gets easier.
Covering some basic rules we follow while creating test cases.
1. Simple helper components from Software Development Kits (SDKs): We don’t need dedicated tests. We cover them in other classes.
2. Custom written simple helper components: Dedicated tests are required. Cover them in other classes, too, without mocking them up.
3. Complex and heavy-weight components: Write dedicated tests and then mock the dedicated tests in other classes for complex components of the product build.
4. DAO repositories: Dedicated tests are not required in the case of simple queries. But data Java Persistence API (JPA) tests are essential when using complex queries. This makes sure that the end result doesn’t change unexpectedly.
5. Services: We use dedicated tests for each service and then mock them up in other services. Services may need a combination of spring test and JUnit tests, based on the service. A more detailed look at this is coming up soon.
Now, that we know the breakdown of when to mock and when to use actuals, let me walk you through the testing strategy required for different layers in detail
1. Date Formatter
2. String Manipulation
1. Components that do heavy calculations
2. Components that make API Calls to other services or establish DB Connections
Step 1 is to understand exactly what the code is supposed to do. At the end of the day, we write tests not to please our managers or get a good SonarQube report, but to ensure that the code works as intended.
Example: Login user function test in auth service
1. New users should be able to login with a valid username and password
2. If the password is not strong, then don’t allow login
3. If the username is taken then, don’t allow login
4. User name does not contain “weird” characters
5. If other credentials like phone or email are already present, then respond with “user already exists”
Positive testing - Make sure the listed features are working as expected.
Negative testing - Most of the time we figure out negatives while writing down features like an existing username or bad password. But there can be more. Like password length, username length, limited special characters, etc.
Null testing - What if username or password or other required details are null or empty?
Exception testing - Is your code throwing exceptions where required and handling it in other cases?
Mock: Make sure your lower order components are tested correctly before mocking. For example, establishing database connections is a costly operation to build out a test, even within a memory database. Thus, it makes sense to mock it up first, especially when it can be easily done. This gives respective stakeholders and developers a chance to to test it too.
Actual: The actual build can be used for constantly changing components or services which are lightweight, such as the date validator, string formatter, etc. For service, most dependencies in a service can be mocked, including other services. However, if the result of one lightweight service is directly dependent on another service, then we prefer using the actual object instead of a mockup. How to determine that? It comes with years and years of practice and there is, unfortunately, no golden rule.
A thorough understanding of the underlying code, however, goes a long way to aid writing good tests.
But let’s go back to one of the first ‘excuses/objections’ you hear, “The code keeps changing rapidly, how do you approach that?”
1. We write the code for the most frequently changing service or component. Never mock it, actually write it. That way, we don’t miss making changes to the codes that are dependent on this constantly changing code, minimizing risk of breaking the code and any last minute surprises.
2. We use actual tests wherever we can. It sounds tedious in the beginning, but as the application starts taking shape, we drastically reduce room for errors, even when adding new features.
3. We use dedicated tests for each service and then mock them up in other services. Services may need a combination of spring test and JUnit tests, based on the service. A more detailed look at this is coming up soon.
Here is the link to the sample application from the Zemoso author.
A version of this blog post was earlier published on Medium.
As a startup studio, we spend a lot of time cultivating engineering chops that allow us to test smarter. It’s crucial for us to accelerate our client’s deployment efforts. On one hand, test cases add an incredible amount of value to the overall quality of application. On the other, they are tough and can take up a considerable amount of time. However, with the right mechanics and systems in place, they can definitely get easier, even for beginners.
1. By getting better code coverage.
2. By creating efficiencies around how long it takes you to write the tests.
3. By improving the overall test strategy
Some of the most common excuses you might hear for not having a unit test for Spring Boot applications (or for any application) is: “We didn’t have the time to write the tests due to too many changes, not enough time, tight deadlines etc. etc." We say: "No excuses. Test cases are crucial and we’re going to write testable code from day one."
But in order to do that, it’s important to learn when to:
1. Mock
2. Inject
3. Use actual objects
And equally important to understand when to:
1. Run simple JUnit test
2. Run Spring Boot test
3. Run data Java Persistence API (JPA) test
Simple example from with favorite holiday dessert
Consider this simple scenario. A cookie manufacturer makes cookies of the same weight and ships a certain quantity to the dealers. What is the easiest way to count the number of cookies sent to the dealer?
Each cookie weighs x ounces,
Then total cookies in each box will be = box weight / x
Total cookies shipped = shipment weight / x
Total boxes in each shipment = shipment weight / box weight
But, and this one is a big but, this calculation holds true only if, each cookie is the exact same weight. So, as long as the manufacturer makes sure that their testing unit weighs the cookie accurately, they will be able to send out the right number of cookies. They might not even need dedicated cookie counting systems.
The same principle applies to testing of software and code. Once we know what the smallest units of our program are, building out the tests and mocking up smaller units gets easier.
Covering some basic rules we follow while creating test cases.
1. Simple helper components from Software Development Kits (SDKs): We don’t need dedicated tests. We cover them in other classes.
2. Custom written simple helper components: Dedicated tests are required. Cover them in other classes, too, without mocking them up.
3. Complex and heavy-weight components: Write dedicated tests and then mock the dedicated tests in other classes for complex components of the product build.
4. DAO repositories: Dedicated tests are not required in the case of simple queries. But data Java Persistence API (JPA) tests are essential when using complex queries. This makes sure that the end result doesn’t change unexpectedly.
5. Services: We use dedicated tests for each service and then mock them up in other services. Services may need a combination of spring test and JUnit tests, based on the service. A more detailed look at this is coming up soon.
Now, that we know the breakdown of when to mock and when to use actuals, let me walk you through the testing strategy required for different layers in detail
1. Date Formatter
2. String Manipulation
1. Components that do heavy calculations
2. Components that make API Calls to other services or establish DB Connections
Step 1 is to understand exactly what the code is supposed to do. At the end of the day, we write tests not to please our managers or get a good SonarQube report, but to ensure that the code works as intended.
Example: Login user function test in auth service
1. New users should be able to login with a valid username and password
2. If the password is not strong, then don’t allow login
3. If the username is taken then, don’t allow login
4. User name does not contain “weird” characters
5. If other credentials like phone or email are already present, then respond with “user already exists”
Positive testing - Make sure the listed features are working as expected.
Negative testing - Most of the time we figure out negatives while writing down features like an existing username or bad password. But there can be more. Like password length, username length, limited special characters, etc.
Null testing - What if username or password or other required details are null or empty?
Exception testing - Is your code throwing exceptions where required and handling it in other cases?
Mock: Make sure your lower order components are tested correctly before mocking. For example, establishing database connections is a costly operation to build out a test, even within a memory database. Thus, it makes sense to mock it up first, especially when it can be easily done. This gives respective stakeholders and developers a chance to to test it too.
Actual: The actual build can be used for constantly changing components or services which are lightweight, such as the date validator, string formatter, etc. For service, most dependencies in a service can be mocked, including other services. However, if the result of one lightweight service is directly dependent on another service, then we prefer using the actual object instead of a mockup. How to determine that? It comes with years and years of practice and there is, unfortunately, no golden rule.
A thorough understanding of the underlying code, however, goes a long way to aid writing good tests.
But let’s go back to one of the first ‘excuses/objections’ you hear, “The code keeps changing rapidly, how do you approach that?”
1. We write the code for the most frequently changing service or component. Never mock it, actually write it. That way, we don’t miss making changes to the codes that are dependent on this constantly changing code, minimizing risk of breaking the code and any last minute surprises.
2. We use actual tests wherever we can. It sounds tedious in the beginning, but as the application starts taking shape, we drastically reduce room for errors, even when adding new features.
3. We use dedicated tests for each service and then mock them up in other services. Services may need a combination of spring test and JUnit tests, based on the service. A more detailed look at this is coming up soon.
Here is the link to the sample application from the Zemoso author.
A version of this blog post was earlier published on Medium.