7 minute read

The plan

Originally, I had planned to include code for the feature toggles in the previous post. Unfortunately, I ran out of time during the week.

In order to provide an example of the different types of feature toggles, I wanted to demonstrate setting-level configuration (determined on start-up of the application) as well as toggle-able results.

Last time we checked in on our silly Spring Boot application, we had gotten data to be returned to the user and were just about to write tests. As of this commit, the basic controller tests have been included.

To demonstrate the feature toggle for the fitness tracker application, I decided to include weight as an attribute to each week’s fitness progress. Every week, I take the average weight tracked on my phone and enter it as a log. The toggle-able feature will be to either return the data in metric (kg) or imperial (lb).

For the application configuration, this weight conversion type can be set in the application.properties on startup. In another article, we can discuss changing these properties on the fly via Spring Cloud Config.

For the toggle-able configuration, I want to be able to send a POST request to change the conversion type on the fly. The example is a bit contrived as the UI complement would usually recalculate values for display. However, without the UI, I still want to be able to demonstrate how to write such a feature toggle.

Now that we’ve clarified our approach, let’s move on to the execution.

The prepwork

First of all, we’ll actually be doing TDD this around. We start with updating the database schema and data to include the new weight value.

CREATE TABLE FITNESS_LOG (
  ID        INT PRIMARY KEY,
  LOG_DATE  DATE,
  PUSHUPS   INT,
  PULLUPS   INT,
  CRUNCHES  INT,
  SUPERMANS INT,
  WEIGHT    DOUBLE
);

An astute observer will also note that there was a new column added to the FITNESS_LOG table.

Beware, if the column is nullable, make sure to use the wrapper type of the primitive or else hibernate will scream.

The data has also been updated:

INSERT INTO FITNESS_LOG (LOG_DATE, PUSHUPS, PULLUPS, CRUNCHES, SUPERMANS, WEIGHT) VALUES
  (18, '2017-04-29', 170, 86, 60, 0, 170.6),
  (17, '2017-04-22', 200, 110, 20, 20, 168),
  (16, '2017-04-15', 210, 111, 50, 0, null),
  ...

Note the null in the records for which we have no available data. Normally, we would test our persistence layer but as we’re using JPA, there’s no need.

The tdd

When I first tried writing this code, I went straight into our existing FitnessLogControllerTest and started adding an endpoint used to set the conversion type. But before that, I couldn’t figure out how to load up the spring boot application with values from the application.properties.

With a little digging, I found that creating a configuration class could work, and be a centralized approach to global configs in an application.

package com.eginwong.physical.fitnesstracker.configuration;

import com.eginwong.physical.fitnesstracker.domain.ConversionType;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

public class ConversionConfigurationTest {

    private static final List<String> METRIC = Arrays.asList("METRIC", "METRIC", "KILOGRAMS", "KILOS", "KG");
    private static final List<String> IMPERIAL = Arrays.asList("IMPERIAL", "METRIC", "KILOGRAMS", "KILOS", "KG");

    private ConversionConfiguration conversionConfiguration;

    @Before
    public void setup() {
        conversionConfiguration = new ConversionConfiguration();
    }

    @Test
    public void set_type_metric() {
        conversionConfiguration.setType("METRIC");
        Assert.assertEquals(ConversionType.METRIC, conversionConfiguration.getType());
    }
}

I want to create a ConversionConfiguration class which will set a value for the conversion type, based on a ConversionType enum:

package com.eginwong.physical.fitnesstracker.domain;

/**
 * The enum Conversion type which determines whether imperial or metric measurement is used.
 */
public enum ConversionType {
    IMPERIAL, METRIC
}

I stubbed out the basic Java class, so my code compiles but I get a fat failure.

tdd-1

In my actual ConversionConfiguration class, I add a static array of accepted values for metric and check if the list contains the input parameter. Note that there’s no mention of imperial, as we’re strictly writing enough code to get the test to pass.

@Component
@ConfigurationProperties("conversion")
public class ConversionConfiguration {

    private static final List<String> METRIC = Arrays.asList("METRIC");
    private ConversionType type;

    public void setType(String conversionType) {
        if (METRIC.contains(conversionType)) this.type = ConversionType.METRIC;
    }
    ...
}

tdd-2

The current test suite seems a little limited, so I’ll test for a few other keywords. I added in the imperial keywords as well, including lowercase and other edge cases. I want to make sure that the default value is set to imperial and that the conversion type should never be null. Test case time!

    @Test
    public void default_conversion_type() {
        Assert.assertEquals(ConversionType.IMPERIAL, conversionConfiguration.getType());
    }

The corresponding code to make this test pass is quite simple:

    public ConversionConfiguration() {
        this.type = ConversionType.IMPERIAL;
    }

Just update the value in the default constructor! (doy) Anyway, let’s move on to the controller.

Actually getting somewhere… 🤠

In the FitnessLogControllerTest, I add a mockbean for the ConversionConfiguration class. This way, I should be able to access my global properties from within my controller.

In order to mock the before and after state of my fitness log, I make use of two private variables in my test: originalFitnessLog and expectedFitnessLog. I include a helper method to set all the required attributes to be identical.

    private FitnessLog originalFitnessLog;
    private FitnessLog expectedFitnessLog;

    @Before
    public void setup() {
        originalFitnessLog = new FitnessLog();
        expectedFitnessLog = new FitnessLog();
        modifyFitnessLog(originalFitnessLog);
        modifyFitnessLog(expectedFitnessLog);
    }

In my conversion test, I make sure to verify my actual result against my expected result.

@Test
public void retrieve_all_fitness_logs_kgs() throws Exception {

    conversionConfiguration.setType("METRIC");
    expectedFitnessLog.setWeight(originalFitnessLog.getWeight() / KG_TO_LBS);

    Mockito.when(fitnessLogRepository.findAll()).thenReturn(Collections.singletonList(originalFitnessLog));
    mockMvc.perform(get("/api/fitness-logs?conversion=lbs"))
            .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(jsonPath("$", hasSize(1)))
            .andExpect(jsonPath("$.[0].crunches").value(expectedFitnessLog.getCrunches()))
            .andExpect(jsonPath("$.[0].pullups").value(expectedFitnessLog.getPullups()))
            .andExpect(jsonPath("$.[0].pushups").value(expectedFitnessLog.getPushups()))
            .andExpect(jsonPath("$.[0].supermans").value(expectedFitnessLog.getSupermans()))
            .andExpect(jsonPath("$.[0].weight").value(expectedFitnessLog.getWeight()))
            .andExpect(jsonPath("$.[0].logDate").value(expectedFitnessLog.getLogDate().toString()));
}

Nice and failed, as expected.

tdd-3

Let’s go ahead and implement the correct logic in the main FitnessLogController class. Haha… 🤯🤯🤯🤯🤯🤯🤯🤯🤯 this didn’t work out as expected.

🤯🤯🤯🤯🤯🤯🤯🤯🤯

I couldn’t wire the mock ConversionConfiguration class properly, especially alongside the MockMVC stuff. I took it as a sign that I was trying to do too much logic in the controller. After a brief sigh, I refactored out the controller logic into a separate service, and then re-did the tests.

Alongside the testing, I wanted to more efficiently check for each of the attributes of my FitnessLog object. Therefore, I went into the POJO class and added built-in equals()/hashcode() methods. This way, I can do:

Assert.assertEquals(expected, actual);

which leads to fun, simple assertion stuff like this:

@Test
public void retrieve_all_fitness_logs_lbs() {
    Mockito.when(fitnessLogRepository.findAll()).thenReturn(Collections.singletonList(originalFitnessLog));
    Assert.assertEquals(Collections.singletonList(expectedFitnessLog), fitnessLogService.retrieveFitnessLogs());
}

Anyway, all the tests pass and we’re finished with the underlying business logic. Now, let’s handle the feature toggling.

Feature toggles, finally

Setting configuration (on startup)

ConversionConfiguration is the right place to start. The aim is to toggle the conversion type based on what’s set in my application.properties file. In order to start with my TDD, I have to look at how I can set these file-based properties in my tests.

Luckily, a coworker of mine was looking into ReflectionUtils for just the trick.

This is the test code that sets the private variables.

@Test
public void property_file_configuration_setting() {
    ReflectionTestUtils.setField(conversionConfiguration, "type", ConversionType.METRIC);
    Assert.assertEquals(ConversionType.METRIC, conversionConfiguration.getType());
    ReflectionTestUtils.setField(conversionConfiguration, "type", ConversionType.IMPERIAL);
    Assert.assertEquals(ConversionType.IMPERIAL, conversionConfiguration.getType());
}

And my corresponding class code that reads from the properties file only uses the annotation @ConfigurationProperties and it’s sufficient to set my private parameters as required.

With my application.properties set to METRIC:

  ...
	{
		"id": 17,
		"logDate": "2017-04-22",
		"pushups": 200,
		"pullups": 110,
		"crunches": 20,
		"supermans": 20,
		"weight": 76.20360878518747
	},
	{
		"id": 18,
		"logDate": "2017-04-29",
		"pushups": 170,
		"pullups": 86,
		"crunches": 60,
		"supermans": 0,
		"weight": 77.38295034972013
	}

Without setting anything in my application.properties:

...
	{
		"id": 17,
		"logDate": "2017-04-22",
		"pushups": 200,
		"pullups": 110,
		"crunches": 20,
		"supermans": 20,
		"weight": 168.0
	},
	{
		"id": 18,
		"logDate": "2017-04-29",
		"pushups": 170,
		"pullups": 86,
		"crunches": 60,
		"supermans": 0,
		"weight": 170.6
	}

The rounding could use some work, but I’m happy as it is. Let’s get on to real-time toggling.

On-the-fly toggling

In the new class ConversionConfigurationService, I want a way to be able to toggle the configuration via REST. I start with the tests and define the exact behaviour I would like to see from my service.

@Test
    public void toggle_metric() {
        conversionConfigurationService.toggle("METRIC");
        Assert.assertEquals(ConversionType.METRIC, conversionConfiguration.getType());
    }

    @Test
    public void toggle_imperial() {
        conversionConfigurationService.toggle("IMPERIAL");
        Assert.assertEquals(ConversionType.IMPERIAL, conversionConfiguration.getType());
    }

    @Test
    public void toggle_plain() {
        // set starting point
        conversionConfigurationService.toggle("IMPERIAL");
        Assert.assertEquals(ConversionType.IMPERIAL, conversionConfiguration.getType());
        conversionConfigurationService.toggle();
        Assert.assertEquals(ConversionType.METRIC, conversionConfiguration.getType());
        conversionConfigurationService.toggle();
        Assert.assertEquals(ConversionType.IMPERIAL, conversionConfiguration.getType());
    }

From the tests, you can see that I can define exactly what state I want the conversion to be in, or I could just toggle between the both of them.

Here is the corresponding feature toggle code:

    public void toggle(String setting) {
        conversionConfiguration.setType(setting);
    }

    public void toggle() {
        if (conversionConfiguration.getType().equals(ConversionType.IMPERIAL)) {
            conversionConfiguration.setType(ConversionType.METRIC);
        } else {
            conversionConfiguration.setType(ConversionType.IMPERIAL);
        }
    }

Not the most elegant solution, but it enables me to toggle this “feature”, so I’m happy. After including the corresponding controller tests, we’re off to the races.

I made the following POST call, using Insomnia: :8080/api/configuration/conversion?type=kgs and things are working smoothly as expected. With tests, fancy that!

For all the code, check out my github.

Anyway, see y’all next time!