Best Practices for JUnit Testing
Learn how to build a unified workflow for functional and security testing in JUnit.
Get the Most Out of JUnit
We summarized everything you need to know (including code examples) to build one unified testing workflow for both security and functional testing in JUnit for you to either download or check out below.
What to Expect on This Page
If you’ve ever worked with Java, you are probably familiar with JUnit. Many consider JUnit the most popular Java testing framework as it is very easy to handle, open-source, and compatible with most other Java environments such as IntelliJ, Eclipse, IDEA, NetBeans, and many more. JUnit is widely known for its name-giving unit testing functionality, which makes it very easy for developers to write, execute and automate unit tests, without interrupting the development workflow. Let’s have a look at some best practices that will help you enhance your JUnit testing setup, to uncover even more functional and security issues.
3 Reasons Why Java Devs Love JUnit
1. It’s easy to use: It has a simple, straightforward API that makes it easy for developers to write and execute tests.
2. It’s fast: JUnit tests run quickly, enabling devs to run a large suite of tests in a short amount of time. This allows them to get quick feedback on the quality of their code.
3. It’s reliable: JUnit provides a framework to write repeatable and deterministic tests, which means they will give the same results every time they are run. This makes it easy for developers to trust the results of their tests and have confidence in their code.
What Is the JUnit Testing Framework Used For?
JUnit can be used for a variety of different tests, such as API tests, browser tests, security tests and many more. However, JUnit testing is most commonly done to ensure that individual units or components of the code are working correctly and as intended, i.e. for functional testing. They are typically run during the development process to find bugs that could cause unexpected behavior or crashes.
JUnit enables developers to integrate automated unit testing into the build process, to run unit tests automatically, with every code change. This also ensures that new code doesn’t introduce any regressions, or breaks existing functionality.
In addition to helping developers catch and fix bugs, unit tests done with JUnit can serve as documentation for the code, as they provide examples of how the code is intended to be used. This is particularly useful when the codebase is large and complex, or when multiple developers are working on the same codebase.
Refresher: What Is JUnit?JUnit is the most popular framework for unit testing in Java, used to write and automate tests. With JUnit, developers can test individual units or components of their code to ensure that they are working correctly and as intended. With JUnit, testing can be automated and integrated into common Java build systems. Find more info about the JUnit testing framework here. |
Functional and Security Testing - Can JUnit Do Both?
The key difference between functional testing and security testing is that functional testing evaluates if an application behaves as it should, i.e., does it do the right thing, given the right, valid input? Usually, black-box testing methods are used for functional testing. Such approaches imitate real-world usage by firing a large number of dynamically generated inputs at the application.
Security testing, however, aims to uncover issues that could expose an application to external threats, such as SQL injections. It’s usually done by simulating attacks and evaluating if an application behaves as it should, given invalid or malicious inputs. For security testing, white-box or grey-box approaches are very well suited, as they take the structure of the application into account when generating test inputs, allowing them to target critical areas actively.
The JUnit testing framework can be used for both functional and security testing. However, in most companies, JUnit is used by the dev team for functional testing, while a dedicated security team is in charge of security issues.
Are Functional and Security Bugs the Same?
Although it’s good to know the difference between functional and security testing, there are devs that believe the distinction doesn’t really matter, since both are equally important and the line between the two can be very fuzzy. For example, a functional bug can lead to a crash, which could then expose an application to exploits. Would you classify this as a security or functional issue? Who would be in charge?
The example might seem exaggerated. Nonetheless, it highlights how a split between functional and security can lead to friction. Instances like these can lead to diffusion of responsibility, where bugs are assigned to one team or another based on interpretation.
The Implications of Shifting Left for Java Developers
The above paragraph basically describes why many dev teams in Java and other ecosystems are shifting left and have been doing so for years. In essence, shifting left means testing software earlier, i.e., further to the left of the development process. This shift has considerable implications for the interaction of dev and security teams, as it requires putting security testing into the hands of developers. This includes automating security testing to an extent where it no longer requires extensive expertise and integrating security tests into CI/CD - exactly like unit testing!
This way, the developers who write the code can take full ownership of both its functionality and security. By continuously integrating security testing into the development process, bugs can be found earlier in the development process, which makes it easier to fix them and speeds up software development overall. Shifting left also enables developers to make tests they’ve written available to their team.
Learn more about DevSecOps here
Try Out CI Fuzz
With CI Fuzz, you can add security to your unit tests in JUnit. CI Fuzz automatically generates millions of unexpected and unusual test inputs that can trigger exactly those functionality, security, and availability issues that unit tests tend to miss. If you've ever run a unit test, you will be able to use it.
How to Build a Unified Workflow for Security & Functional Tests in JUnit
The best way to build better Java software is to equip developers with tools that enable them to test their code themselves as a solidified part of the development process. With functional testing, this is already common practice within JUnit. Since JUnit provides an amazing playground for test automation, we (Code Intelligence) built an integration that enables security testing within JUnit using @FuzzTest.
Having unit testing and fuzz testing in a unified, developer-friendly workflow enables dev teams to close blind spots before merging. In such a setup, unit tests cover deterministic test cases of known use cases or test scenarios, while fuzz tests cover non-deterministic outcomes by automatically generating dynamic test cases to trigger interesting or unusual program states. Although most of the issues found with fuzz testing are security-related, fuzz testing can also help to find hidden functional bugs.
All of this will become very clear if we look into actual code.
Adding Unit and Fuzz Tests to JUnit
So let’s say you have a method that inserts data into a database. How would you write a test for this?
Let’s start with this:
public class InformationDatabaseTest {
private InformationDatabase db;
@Test
public void insertDataUnitTest(){
}
}
One approach to write a good test is to use the “arrange, act, assert” pattern:
public class InformationDatabaseTest {
private InformationDatabase db;
@Test
public void insertDataUnitTest(){
// arrange our objects and test fixtures
// act on an object to perform a test
// assert the result of the action is as expected
}
}
If we add some code to do these steps, it looks like this
public class InformationDatabaseTest {
private InformationDatabase db;
private InformationDatabase initializeDatabase(){
InformationDatabase db = new InformationDatabase();
db.connect();
return db;
}
@Test
public void insertDataUnitTest(){
// arrange our objects and test fixtures
db = initializeDatabase(); // initialize db object to test
Road road = new Road("Germany"); // create a valid Road object to insert into db
// act on an object to perform a test
Boolean result = db.insertRoadData(road);
// assert the result of the action is as expected
Assert.assertTrue(result);
}
}
This test runs and passes. We now have a unit test to verify that this method works!
But notice that we’re hard-coding a value for our test:
@Test
public void insertDataUnitTest(){
// arrange our objects and test fixtures
db = initializeDatabase(); // initialize db object to test
Road road = new Road("Germany"); // create a valid Road object to insert into db
// act on an object to perform a test
Boolean result = db.insertRoadData(road);
// assert the result of the action is as expected
Assert.assertTrue(result);
}
This only validates the method for this single value. What about other values? Also, what about values that could cause problems but we didn’t think of? Or values we aren’t sure how to generate? There has to be a better way.
So let's add a fuzz test to our unit test to intelligently generate input values. First, we need to copy and paste our unit test into a new test and put in some fuzzy input:
public class InformationDatabaseTest {
private InformationDatabase db;
private InformationDatabase initializeDatabase(){
InformationDatabase db = new InformationDatabase();
db.connect();
return db;
}
@Test
public void insertDataUnitTest(){
// arrange our objects and test fixtures
db = initializeDatabase(); // initialize db object to test
Road road = new Road("Germany");
// act on an object to perform a test
Boolean result = db.insertRoadData(road);
// assert the result of the action is as expected
Assert.assertTrue(result);
}
public void insertDataFuzzTest(){
// arrange our objects and test fixtures
db = initializeDatabase(); // initialize db object to test
Road road = new Road("*78?90?**c4C"); // create a valid Road object to insert into db
// act on an object to perform a test
Boolean result = db.insertRoadData(road);
// assert the result of the action is as expected
Assert.assertTrue(result);
}
}
The fuzz test looks like it might work the same way as our unit test does. Great! Let’s get some intelligence in there, by adding objects that will generate smart data for us:
public class InformationDatabaseTest {
private InformationDatabase db;
private InformationDatabase initializeDatabase(){
InformationDatabase db = new InformationDatabase();
db.connect();
return db;
}
@Test
public void insertDataUnitTest(){
// arrange our objects and test fixtures
db = initializeDatabase(); // initialize db object to test
Road road = new Road("Germany"); // create a valid Road object to insert into db
// act on an object to perform a test
Boolean result = db.insertRoadData(road);
// assert the result of the action is as expected
Assert.assertTrue(result);
}
@FuzzTest
public void insertDataFuzzTest(FuzzedDataProvider fuzzedData){
// arrange our objects and test fixtures
db = initializeDatabase(); // initialize db object to test
Road road = new Road(fuzzedData.consumeString(10)); // generate fuzzy inputs
// act on an object to perform a test
Boolean result = db.insertRoadData(road);
// assert the result of the action is as expected
Assert.assertTrue(result);
}
}
Next, we add some additional properties to tell JUnit that this is a fuzz test:
@FuzzTest
public void insertDataFuzzTest(FuzzedDataProvider fuzzedData){
// arrange our objects and test fixtures
db = initializeDatabase(); // initialize db object to test
Road road = new Road(fuzzedData.consumeString(10)); // generate fuzzy inputs
// act on an object to perform a test
Boolean result = db.insertRoadData(road);
// assert the result of the action is as expected
Assert.assertTrue(result);
}
We need a fuzzedData object to retrieve the generated data for our test, and in this case, we need a string value. This is where the magic happens!
And we’ve now created a fuzz test from a unit test, and the two can be run as needed.
Now that we’ve seen how to write a unit test and fuzz test using JUnit, let’s see what these tests produce when they’re created.
Running The Tests
Running a unit test should be fast and easy, regardless of the programming language or testing tool used. This is the case with JUnit, which is integrated with all major Integrated Development Environments (IDEs) such as IntelliJ, Eclipse or Visual Studio Code. Tests can be run with either the click of a button or using a build tool such as Maven.
Since unit tests are functional tests, the result of a unit test is typically a passed or failed status. Running the above @UnitTest produces the following output:
The test passes and produces a green checkmark as a result.
cifuzz run InformationDatabaseTest
The result looks like this:
Here we can see information about the test run: how long the test took to run, how many findings cifuzz found and the name of specific findings that were found. In this case, cifuzz found one SQL Injection Issue and called it poetic_amoeba.
Try It Yourself
Feel free to replicate our findings yourself using RoadSmart, the GitHub sample project which the code above was taken from. You can copy all of the code snippets on this page, or simply follow the instructions in our documentation. If you want to learn more about CI Fuzz, check out our product tour.