Simplify Unit Testing with Fixture Monkey

SeongAh Jo
NAVER Platform Labs
5 min readNov 4, 2023

Writing effective unit tests is an essential part of software development. Unit tests help ensure that your code functions correctly and continues to do so as it evolves. However, creating comprehensive unit tests often involves setting up test data, and this can be a time-consuming and repetitive task. This is where Fixture Monkey comes to the rescue. In this article, we’ll explore Fixture Monkey, a library that simplifies the process of creating test fixtures in Kotlin and Java projects.

The Challenge of Test Data Setup

Photo by Scott Graham on Unsplash

Imagine you’re writing unit tests for a complex system with many classes, each dependent on specific data structures. Manually creating test data for these classes can be cumbersome. This process can become especially daunting when dealing with a hierarchy of classes, interfaces, and their dependencies.

Different classes may have different constructors, each requiring specific arguments. Keeping track of these variations and writing code to accommodate them adds complexity and cognitive overhead. In some cases, classes are designed to be instantiated through factory methods. Managing these factory methods and ensuring correct usage can also be challenging.

This is where Fixture Monkey steps in to make your life as a developer easier.

Introducing Fixture Monkey

Fixture Monkey is a versatile library designed to help you generate test data with minimal effort. It offers five key principles:

1. Concise

Fixture Monkey provides a straightforward and concise way to create test fixtures in your code. With just a single line of code, you can instantiate objects with randomly populated data, making your test code more readable and maintainable.

2. Pragmatic

Fixture Monkey is designed with practicality in mind. It offers features that address your everyday testing needs. Whether you want to generate random or specific data, Fixture Monkey simplifies the process, allowing you to focus on writing meaningful tests rather than struggling with data setup.

3. Interoperable

One of Fixture Monkey’s strengths is its interoperability. It seamlessly works with both Kotlin and Java projects. Whether your team prefers one language over the other, Fixture Monkey can serve as a common solution for generating test data.

4. Non-intrusive

One of the significant advantages of Fixture Monkey is that it doesn’t require any configuration or changes to your production code. You can use it exclusively in your test code, ensuring that your production code remains unchanged and free from any unnecessary complexities.

5. Reducing cognitive load

Fixture Monkey unifies the process of creating objects for testing by providing a simplified, consistent approach. Fixture Monkey provides flexibility in how objects are instantiated. Testers can choose to create objects using constructors or factory methods or mixed, depending on their specific testing needs. This flexibility simplifies the process of creating objects in different scenarios, reducing cognitive overhead.

Creating Test Fixtures with Fixture Monkey

Fixture Monkey can generate a test object with a single line of code, regardless of the complexity of the type. Let’s delve into some complex examples of how Fixture Monkey simplifies test data creation.

The example tests below will use this instance of Fixture Monkey.

val fixtureMonkey= FixtureMonkey.builder().plugin(KotlinPlugin()).build()

Customizing object properties

@Test
fun set() {
// given
class Foo(val bar: String, val baz: List<String>)
val expected = "set"

// when
val actual = fixtureMonkey.giveMeBuilder<Foo>()
.setExp(Foo::bar, expected)
.sizeExp(Foo::baz, 3)
.setExp(Foo::baz[1], expected)
.sample()

// then
then(actual.bar).isEqualTo(expected)
then(actual.baz[1]).isEqualTo(expected)
}

In the code example, you create a Foo and customize one of its properties, bar, by setting it to the expected value, set. This customization allows you to control the value of the property for the test, ensuring that it meets the requirements of your specific testing scenario.

The size operation in Fixture Monkey allows you to specify the size of a collection property within an object. In the code example, you use it to set the size of the baz property in the Foo object to 3.

you can use the Kotlin DSL to access a nested property. Foo::baz refers to the baz property within the Foo class, which is a list (or array) of strings. [1] signifies that you are accessing the element at index 1 of this list.

When writing unit tests, it’s often essential to test specific scenarios with known data. By customizing object properties, you can precisely define the data you want to use in your tests, making it easier to verify the behavior of your code in those specific situations. This precision in testing is especially valuable for edge cases and corner scenarios.

Creating an anonymous instance

interface AnonymousObject {
val string: String
val int: Int
}

@Test
fun sampleAnonymousObject() {
// when
val actual: AnonymousObject = fixtureMonkey.giveMeOne()

// then
then(actual.string).isNotNull
then(actual.int).isNotNull
}

Fixture Monkey can generate an AnonymousInstance object with random or predefined values. Testers no longer need to manually create anonymous objects when writing unit tests.

Consistent instantiation

@Test
fun unifiedInstantiate(){
// given
class Foo(val bar: String, val baz: Int){

companion object {
fun build(baz: Int): Foo = Foo("factory", baz)
}
}

// when
val constructor = fixtureMonkey.giveMeBuilder<Foo>()
.instantiateBy { constructor() }
.sample()

val factory = fixtureMonkey.giveMeBuilder<Foo>()
.instantiateBy { factory("build") }
.sample()

// then
then(constructor.bar).isNotEqualTo("factory")
then(factory.bar).isEqualTo("factory")
}

Fixture Monkey create a Foo object, specifying that it should be instantiated using any constructor within the class. This is achieved with the instantiateBy { constructor() } method call.

Similarly, you use Fixture Monkey to create another Foo object, but this time, you specify that it should be instantiated using the factory method build from the companion object. This is done with the instantiateBy { factory("build") } method call.

This flexibility is valuable when you want to test different object creation paths within your code or when you have specific requirements for object instantiation in your unit tests.

Conclusion

Fixture Monkey is a valuable tool for simplifying unit testing in your Kotlin and Java projects. Its conciseness, pragmatism, and interoperability make it an excellent choice for generating test fixtures, whether you’re working with complex class hierarchies, interfaces, or straightforward data structures. By adopting Fixture Monkey, you can streamline your testing process, increase test coverage, and write more robust and reliable code.

To get started with Fixture Monkey and make your unit testing journey smoother, visit the Fixture Monkey website and follow the installation and usage guides. Happy testing!

🔗 Github
📄 Documentation

--

--