How Earnin is Vaporizing Boilerplate

Josiah Roberts
Tech @ Earnin
Published in
3 min readSep 19, 2018

Earnin has a fast-paced development environment. With so many parallel initiatives, projects, and experiments running concurrently, we lean heavily on automated testing to make sure the code we’re shipping every day works as intended and does not negatively impact our users. However, not all testing is created equal, and over time we have found it increasingly cumbersome to write and maintain our suite of tests, often spending more time trying to understand the test code than the code under test.

We took a step back to look at the way we were testing, it became clear that we were spending a lot of time writing code that wasn’t testing functionality. This extra code was setting up the system under test, generating test data, and setting up mocks. This code was very much the same from test to test, but was still adding to our workload as we had to reinvent the testing wheel over and over in each test.

An example

To give an example, here’s a minimalistic test we might have for a function that called a dependency and added one. Note: we’re using MSTest and Moq here.

[TestClass]
public class Test
{
[TestMethod]
public void AddsOne()
{
var rng = new Random();
var number = rng.Next(1000);
var mockDependency = new Mock<IDependency>();
mockDependency.Setup(x => x.GetNumber())
.Returns(number);
var testObject = new MyObject(mockDependency.Object); var result = testObject.AddOne();
Assert.AreEqual(number + 1, result);
}
}

In this example, only four lines are truly relevant to this test — setting up GetNumber() and calling and verifying the return of AddOne(). Everything else is boilerplate setup (18 lines of it) and the writer of the test must consider how to generate test data, construct mocks, and instantiate the test object.

The situation worsens with more dependencies and more test data. For instance, any test that needed to verify behavior across a condition that could be true or false or multiple enumeration values had to be written with a loop.

In order to simplify our tests and limit the code we needed to write to just the parts verifying the behavior we care about, we switched from MSTest to xUnit for its extensibility and theory support, and developed tools that set up our tests for us.

Automating mocking with MoqFixture

To avoid having to set up dependency mocks and the object under test over and over, we extended MoqFixture. This allows us to declaratively inform our framework, “I’d like to test class x, please set it up for me.” Within this framework, the test above becomes the following:

[TestClass]
public class Test : MoqFixture<MyObject>
{
[TestMethod]
public void AddsOne()
{
var rng = new Random();
var number = rng.Next(1000);
Mock<IDependency>().Setup(x => x.GetNumber())
.Returns(number);
var result = TestObject.AddOne();
Assert.AreEqual(number + 1, result);
}
}

The logic creating mocked dependencies and instantiating the object under test is abstracted away by MoqFixture, which reflects on the type under test and creates any needed dependency mocks. This means that the programmer no longer needs to think about these boilerplate details and allows them to focus on verifying the object’s behavior.

Creating data driven tests

Another type of boilerplate we found ourselves repeatedly writing was code to generate test values, exercise both sides of conditions, etc. By extending xUnit theories, we can automate the generation of test cases and use the arguments of the test method to supply test data:

public class Test : MoqFixture<MyObject>
{
[Hypothesis, ProvideArgs]
public void AddsOne(int number)
{
Mock<IDependency>().Setup(x => x.GetNumber())
.Returns(number);

var result = TestObject.AddOne();
Assert.Equal(number + 1, result);
}
}

We can now configure our test method to be called many times with a variety of numbers without having to worry in the test itself about what that number will be.

So what?

By now, we have a test that has been reduced by half but is still testing the same functionality. This sort of declarative testing has helped Earnin maintain a high level of test coverage without spending an inordinate amount of time repeating boilerplate.

At a fast-paced shop like Earnin, it can be tempting to focus solely on the next feature to implement, but by improving tooling we can make investments that will pay productivity dividends for all our future work.

Photo by Burst on Unsplash

--

--