In our previously published blog post I introduced you to trackr, our time tracking tool. trackr is build upon Java 8, Spring 4, Spring Data REST and Spring Security for the backend and AngularJS for the frontend. I also gave you a glance at how and what we tested in the backend.

In this post I’m going to give detailed instructions on how to set up integration tests for a Spring-powered REST service, testing your web security. It is extremely important to test this as removing security features probably will not trigger failures in your application or in any other tests but may, well, decrease security.

Our goal is having tests that look like this:

public class EmployeeResourceTest extends AbstractResourceTest<Employee> {
    @Test
    public void rootWithAdmin() throws Exception {
        assertThat(rootWith(admin()), isAccessible());
    }

    @Test
    public void rootWithEmployee() throws Exception {
        assertThat(rootWith(employee()), isForbidden());
    }

    @Override
    protected String getResourceName() {
        return "employees";
    }
}

We will create a small DSL (Domain Specific Language) to test the security of our exported web methods.

Example Code

On our GitHub account we provide an example application that goes along with this blog post. The software requirements for the application are:

  • Java 8 JDK.
  • A Java 8 compatible servlet container (I used Tomcat 8).

Setup

Clone the code from here. Gradle is provided via a wrapper so you can just run ./gradlew build to build the WAR file.

If you import the project into your IDE be sure to set the -parameters compiler flag for javac, this enables the Java 8 parameter name reflection feature which will be used by Spring Security. IntelliJ users can do this here:

IDEA Parameters Flag

Running

If you build the WAR file with Gradle it will be in build/libs/spring-test-example-1.0.war. You can copy it to the webapps folder and start the servlet container. Now you can ensure the application is running by using cURL.

curl http://localhost:8080/spring-test-example-1.0/
{
  "_links" : {
    "employees" : {
      "href" : "http://localhost:8080/spring-test-example-1.0/employees{?page,size,sort}",
      "templated" : true
    }
  }
}

If you start the container from within your IDE the application context may be in the root, so you can leave the spring-test-example-1.0/ out.

Functional Requirements

Let’s have a quick overview of our functional requirements.

  • The application exposes one entity via a REST service. This entity is an employee using trackr and has a name and salary. There are two users of the service, employees and admins. Employees may only access their own profile, while admins are allowed to see all employees.
  • Creating and updating employees is only allowed for admins.
  • Deleting employees is not exported via the REST service.
  • There is also one search method to find employees by their name. Again, employees can only search for themselves.

Technical Implementation

I will not go into detail as this post is about the testing aspect. After setting up Spring Security and Spring Data REST only two classes are of concern for the functional requirements: The EmployeeRepository and the EmployeeEventHandler. Everything is controlled using annotations.

The authentication in this example application is just basic HTTP authentication. There are two users built in already, admin and employee. Their password equals their username.

Testing the Functional Requirements With cURL

If you have deployed the application and it works you can test out the features like so (for the rest of this post I’m going to assume the application is deployed under root).

curl -v http://localhost:8080/employees
...
< HTTP/1.1 401 Unauthorized

curl -v http://employee:employee@localhost:8080/employees
...
< HTTP/1.1 403 Forbidden

curl http://admin:admin@localhost:8080/employees
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/employees{?page,size,sort}",
      "templated" : true
    },
    "search" : {
      "href" : "http://localhost:8080/employees/search"
    }
  },
  "_embedded" : {
    "employees" : [ {
      "name" : "Jon",
      "salary" : 100000,
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/employees/0"
        }
      }
    }, {
      "name" : "Moritz",
      "salary" : 75000,
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/employees/1"
        }
      }
    } ]
  },
  "page" : {
    "size" : 20,
    "totalElements" : 2,
    "totalPages" : 1,
    "number" : 0
  }
}

curl -v -X DELETE http://admin:admin@localhost:8080/employees/0
...
< HTTP/1.1 405 Method Not Allowed

curl -v -X PUT http://employee:employee@localhost:8080/employees/1 --header "Content-Type: application/json" --data "{\"name\": \"Test\", \"salary\": 400}"
...
< HTTP/1.1 403 Forbidden

curl -v -X PUT http://admin:admin@localhost:8080/employees/1 --header "Content-Type: application/json" --data "{\"name\": \"Test\", \"salary\": 400}"
...
< HTTP/1.1 204 No Content

# Other URLs to try out via GET:
# http://employee:employee@localhost:8080/employees/search/findByName\?name\=Jon
# http://employee:employee@localhost:8080/employees/search/findByName\?name\=Moritz
# http://admin:admin@localhost:8080/employees/search/findByName\?name\=Moritz
# http://employee:employee@localhost:8080/employees/0
# http://employee:employee@localhost:8080/employees/1
# http://admin:admin@localhost:8080/employees/1

Unfortunately, Spring Data REST has some small bugs. For example, if an employee issues a PUT request to another employee he will get an HTTP code 400 instead of 403.

The Tests

Now let’s get to the part where we write the tests. We will start with a basic MockMvc test and continue with the desired DSL.

MockMvc

Our testing approach is based on Spring’s MockMvc. It’s a very powerful tool to test Spring MVC applications. Since Spring Data REST uses Spring MVC internally we can use MockMvc.

A basic setup of a test using MockMvc looks like this:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {WebConfiguration.class, AppConfiguration.class}) //load your mvc config classes here
public class MockMvcTest {
	protected MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public final void initMockMvc() throws Exception {
        mockMvc = webAppContextSetup(webApplicationContext).build();
    }

	@Test
    public void rootWitAdmin() throws Exception {
		mockMvc.perform(
        	get("/employees")
        )
			.andExpect(status().isOk())
			.andExpect(content().contentType("application/hal+json"));
    }
}

In this example I loaded the AppConfiguration which will boot the Spring Data repositories and the WebConfiguration which will use Spring Data REST to export them. The content type is the one Spring Data REST uses.

Our goal is to test the security of the exported methods, so let’s add the Spring Security configuration and tell MockMvc to use it.

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {SecurityConfiguration.class, MethodSecurityConfiguration.class, WebConfiguration.class, AppConfiguration.class})
public class MockMvcTest {
	protected MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

	@Autowired
    private FilterChainProxy filterChainProxy;

    @Before
    public final void initMockMvc() throws Exception {
        mockMvc = webAppContextSetup(webApplicationContext).addFilter(filterChainProxy).build();
    }

	@Test
    public void rootWithAdmin() throws Exception {
		mockMvc.perform(
        	get("/employees")
        )
			.andExpect(status().isOk())
			.andExpect(content().contentType("application/hal+json"));
    }

	@Test
	public void createWithAdmin() throws Exception {
		String employeeJson = "{\"name\": \"Jane\", \"salary\": 80000}";//or create this from an employee object
		mockMvc.perform(
			post("/employees")
		)
			.andExpect(status().isCreated());
    }
}

As you can see, we just autowired the FilterChainProxy and added it to the MockMvc builder. Sadly the test will now fail. We haven’t told MockMvc to “login” or something else!

Adding Fake Sessions and Authentication

We have to smuggle some authentication object into the session that MockMvc uses. The request builders (e.g. the get() method you see above) support setting the session. MockMvc already comes with a MockHttpSession. The trick is to set a specific attribute in the session to a fake SecurityContext. A SecurityContext holds an authentication object which we will fake, too. Let’s start with that.

public class AuthenticationMocks {

    private AuthenticationMocks() {
    }

    public static Authentication employeeAuthentication(Long id) {
        TechdevUser techdevUser = new TechdevUser(() -> "ROLE_EMPLOYEE", "employee", "employee", id);
        return new TestingAuthenticationToken(techdevUser, null, "ROLE_EMPLOYEE");
    }

    public static Authentication adminAuthentication(Long id) {
        TechdevUser techdevUser = new TechdevUser(() -> "ROLE_ADMIN", "admin", "admin", id);
        return new TestingAuthenticationToken(techdevUser, null, "ROLE_ADMIN");
    }
}

We just provide static methods to generate an authentication for an employee and an admin. We use our own UserDetails implementations for the principal because we need the id attribute (it is used in the @PreAuthorize annotations). The id is settable so we can fake different users which will become very handy if we try to access one employee with another (I’m not sure if the duplicating of the GrantedAuthorities is necessary).

Next we build a simple MockSecurityContext:

public class MockSecurityContext implements SecurityContext {
    private Authentication authentication;

    public MockSecurityContext(Authentication authentication) {
        this.authentication = authentication;
    }

    @Override
    public Authentication getAuthentication() {
        return authentication;
    }

    @Override
    public void setAuthentication(Authentication authentication) {
        this.authentication = authentication;
    }
 }

Nothing fancy here, it just encapsulates an Authentication. Finally we can build a MockHttpSession to be used in our tests.

public class MockMvcTest {
	//...
    private MockHttpSession buildSession(Authentication authentication) {
        MockHttpSession session = new MockHttpSession();
        session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, new MockSecurityContext(authentication));
        return session;
    }

    /**
     * @return a session for an employee with the id 500.
     */
    protected MockHttpSession employee() {
        return employee(500L);
    }

    protected MockHttpSession employee(Long id) {
        return buildSession(AuthenticationMocks.employeeAuthentication(id));
    }

    /**
     * @return a session for an admin with the id 0.
     */
    protected MockHttpSession admin() {
        return admin(0L);
    }

    protected MockHttpSession admin(Long id) {
        return buildSession(AuthenticationMocks.adminAuthentication(id));
    }
}

The interesting part is where we set the attribute on the session. Spring security will now use our fake Authentication when evaluating security annotations. The helper methods allow us to easily get a fake session in a test.

Now we can change our failing test to:

public class MockMvcTest {
	//...
	@Test
    public void rootWithAdmin() throws Exception {
		mockMvc.perform(
        	get("/employees")
				.session(admin())
        )
			.andExpect(status().isOk())
			.andExpect(content().contentType("application/hal+json"));
    }

	@Test
	public void createWithAdmin() throws Exception {
		String employeeJson = "{\"name\": \"Jane\", \"salary\": 80000}";
		mockMvc.perform(
			post("/employees")
				.session(admin())
		)
			.andExpect(status().isCreated());
    }
}

And it will work!

This is how we started writing web security tests for trackr. But after a while it got very tedious, it’s always the same. In trackr we have three roles instead of two, sometimes we have to test a method three times with different users. So let’s find a way to make this easier!

DataOnDemand for tests

This idea is borrowed from Spring Roo. We will write small “generators” for entities that use the database under the hood. They will also initialize the database with test objects upon start. Here is the (relevant part of the) interface a DataOnDemand object provides:

public abstract class AbstractDataOnDemand<S> {

    public S getRandomObject() { ... }

    public void init() { ... }

	public abstract S getNewTransientObject(int i);
}

We can request a random object. There’s a method to initialize the pool with some elements and we have to implement how to generate a new transient object. For our class Employee it would look like this:

public class EmployeeDataOnDemand extends AbstractDataOnDemand<Employee> {

    @Override
    public Employee getNewTransientObject(int i) {
        Employee employee = new Employee();
        employee.setName("name_" + i);
        employee.setSalary(i);
        return employee;
    }

}

The DataOnDemand objects will be useful for the next section.

If you’re interested on how I implemented the AbstractDataOnDemand class, you can see it here.

Setting up a Base Class for Resource Tests

In this step we will create a base class that provides helper methods to do common actions on the web service such as “get one element”, or “delete one element” with MockMvc. We start by abstracting our previously defined MockMvcTest class and extending it.

public abstract class AbstractResourceTest<T> extends MockMvcTest {

    @Autowired
    private CrudRepository<T, Long> repository;

    @Autowired
    private AbstractDataOnDemand<T> dataOnDemand;

    protected abstract String getResourceName();

    protected abstract String getJsonRepresentation(T item);

    @Before
    public void setUp() throws Exception {
        dataOnDemand.init();
    }
}

The AbstractResourceTest has a type parameter which is the entity we want to test. We make use of one of Spring 4’s new features, autowiring of generic types. If T = Employee this will find the EmployeeRepository and autowire it, like so with the EmployeeDataOnDemand. You can already see one minor restriction, with this implementation you have to use Long as ids for your entities. This can be circumvented with a second type parameter for the AbstractResourceTest.

If we extend AbstractResourceTest we have to tell it how our exported resource is named, i.e. the base path for URLs. We also have to define a method how to get a JSON String out of an item if we want to test PUT and POST.

Now let’s define some helper methods that use MockMvc.

public abstract class AbstractResourceTest<T> extends MockMvcTest {
	//...
	protected ResultActions rootWith(MockHttpSession session) throws Exception {
        return mockMvc.perform(
                get("/" + getResourceName())
                        .session(session)
        );
   	}

	protected ResultActions oneWith(MockHttpSession session) throws Exception {
        return oneWith((object) -> session);
    }

    protected ResultActions oneWith(Function<T, MockHttpSession> sessionProvider) throws Exception {
        T randomObject = dataOnDemand.getRandomObject();
		return mockMvc.perform(
                get(sessionProvider.apply(randomObject), "/" + getResourceName() + "/" + getId(randomObject))
                        .session(session)
        );
	}

	protected ResultActions createWith(MockHttpSession session) throws Exception {
        return createWith((object) -> session);
    }

    protected ResultActions createWith(Function<T, MockHttpSession> sessionProvider) throws Exception {
        T newObject = dataOnDemand.getNewTransientObject(500);
        return mockMvc.perform(
                post("/" + getResourceName() + "/")
                        .session(sessionProvider.apply(newObject))
                        .content(getJsonRepresentation(newObject))
        );
    }
	//DELETE and PUT look similar, cf. provided code.
}

All methods return the ResultActions object so the expectations can be written in the extending test.

The rootWith method is used to access the root of an entity with a given session.

The other two displayed methods come in two flavors: One simply takes a MockHttpSession and uses it, the other one takes a new Java 8 Function from the entity to a MockHttpSession and uses this. We will see later why we need this.

For the moment we are now able to write our test like this.

public class EmployeeResourceTest extends AbstractResourceTest<Employee> {

	@Override
	protected String getResourceName() {
		return "employees";
	}

	@Override
	protected String getJsonRepresentation(T item) { ... };

	@Test
    public void rootWithAdmin() throws Exception {
		rootWith(admin())
			.andExpect(status().isOk())
			.andExpect(content().contentType("application/hal+json"));
    }

    @Test
	public void createWithAdmin() throws Exception {
		createWith(admin())
			.andExpect(status().isCreated());
	}
}

We have also added a test for POSTing a new employee. The base test will take care of getting a JSON representation of a new employee to POST! Previously we had to repeat this in each POST test.

Testing the Forbidden

Until now we have tested if some methods are accessible. But we also want to test if something is forbidden for a specific user.

Remember our functional requirements? The root of employees must not be accessible to employees. Let’s write a test for that.

public class EmployeeResourceTest extends AbstractResourceTest<Employee> {
	//...
	@Test
    public void rootWithEmployee() throws Exception {
		rootWith(employee())
			.andExpect(status().isForbidden());
    }
}

That was easy! Next requirement: An employee may access him/herself, but not other employees. This is where we need the helper methods that take functions instead of MockHttpSessions as parameters.

public class EmployeeResourceTest extends AbstractResourceTest<Employee> {
	//...
	private final Function<Employee, MockHttpSession> owningEmployee;
    private final Function<Employee, MockHttpSession> otherEmployee;

    public EmployeeResourceTest() {
        owningEmployee = employeeObject -> employee(employeeObject.getId());
        otherEmployee = employeeObject -> employee(employeeObject.getId() + 1);
    }

	@Test
	public void oneWithOwning() throws Exception {
		oneWith(owningEmployee)
			.andExpect(status().isOk());
	}

	@Test
	public void oneWithOther() throws Exception {
		oneWith(otherEmployee)
			.andExpect(status().isForbidden());
	}
}

Wow, looks easy, too! What happens here? Remember, oneWith in AbstractResourceTest gets a random employee from the DataOnDemand and accesses it. We now tell oneWith how to create an employee MockHttpSession out of an employee entity with our owningEmployee and otherEmployee functions. In the case of owningEmployee the session will contain a principal that has the id of the employee we want to access, so the Spring Security annotation will allow the access!

In the constructor we use the helper methods to create a MockHttpSession with a given id.

Writing Own Matchers

In the previous step we abstracted the call to mockMvc.perform(), but the expectations to the result have to be repeated in every test. We will write our own Hamcrest matchers to do this for us. This will be the final step towards our small DSL.

We want to use Hamcrest’s assertThat in this way:

assertThat(oneWith(otherEmployee), isForbidden())

So we have to define the isForbidden() matcher. Straight forward to my implementation:

public class DomainResourceTestMatchers {

    @FunctionalInterface
    private static interface ConsumerWithException<T> {
        public void accept(T item) throws Exception;
    }

    private static class ResultActionsMatcher extends TypeSafeMatcher<ResultActions> {
        private String description;
        private ConsumerWithException<ResultActions> test;

        protected ResultActionsMatcher(String description, ConsumerWithException<ResultActions> test) {
            this.description = description;
            this.test = test;
        }

        @Override
        protected boolean matchesSafely(ResultActions item) {
            try {
                test.accept(item);
                return true;
            } catch (Exception e) {
                return false;
            }
        }

        @Override
        public void describeTo(Description description) {
            description.appendText(this.description);
        }
    }

    public static Matcher<? super ResultActions> isAccessible() {
        return new ResultActionsMatcher("accessible", resultActions -> {
            resultActions
                    .andExpect(status().isOk());
        });
    }

    public static Matcher<? super ResultActions> isCreated() {
        return new ResultActionsMatcher("created", resultActions -> {
            resultActions
                    .andExpect(status().isCreated());
        });
    }

    public static Matcher<? super ResultActions> isForbidden() {
        return new ResultActionsMatcher("forbidden", resultActions -> {
            resultActions.andExpect(status().isForbidden());
        });
    }
	//some more
}

Basically all the work is done by ResultActionsMatcher. It takes a new Java 8 Consumer of ResultActions and accepts it if it throws no Exception while executing, otherwise rejects it. We have to define our own Consumer because the JDK’s one does not allow throwing Exceptions.

With this base class we can easily declare own matchers by implementing the consumer. In this example they are really small and maybe don’t seem worth the effort. But you can chain as many andExpect calls as you like. And the naming strategy helps our DSL.

We can now import these matchers into our test and finally arrive at our goal:

public class EmployeeResourceTest extends AbstractResourceTest<Employee> {
	//...
	@Test
    public void rootWithAdmin() throws Exception {
        assertThat(rootWith(admin()), isAccessible());
    }

    @Test
    public void rootWithEmployee() throws Exception {
        assertThat(rootWith(employee()), isForbidden());
    }

	@Test
	public void oneWithOwning() throws Exception {
		assertThat(oneWith(owningEmployee), isAccessible());
	}

	@Test
	public void oneWithOther() throws Exception {
		assertThat(oneWith(otherEmployee), isForbidden());
	}

	@Test
	public void createWithAdmin() throws Exception {
		assertThat(createWith(admin()), isCreated());
	}

	@Test
	public void deleteNotExported() throws Exception {
		assertThat(removeWith(admin()), isMethodNotAllowed());
	}
}

And if we add a second entity with a repository (and provide a DataOnDemand object for it) we can use AbstractResourceTest in the very same way for it!

I hope you had fun reading this article and maybe can even use the ideas in your own project!

Stay in the Loop

If you would like to receive an email every now and then with new articles, just sign up below. We will never spam you!