Easy Unit Testing JSF Backing Beans

It’s not secret that JSF is not the best testable web framework of all. When it comes to Unit Testing JSF backing(managed-bean) logic, the reason for this is quite obvious. At some point when programming the backend logic of your page, you need to use FacesContext to access FacesMessages, session or etc. So how this can be avoided?

PrimeFaces Optimus features an IOC container built on top of Google Guice that goes beyond the capabilities of JSF’s core IOC. Some of these are constructor&field injection, AOP support, easy testing.

To demonstrate how easy it’s easy to test JSF backing beans powered by Optimus, I’m going to implement a simple login scenario. There’re three classes involved in this example; LoginService, LoginServiceImpl and LoginController.

LoginService

public interface LoginService {

	public boolean login(String username, String password);

}

LoginServiceImpl

public class LoginServiceImpl implements LoginService {

	public boolean login(String username, String password) {
		//Connect to a datasource(ldap, db) and actually validate user
		//return outcome
	}

}

LoginService and it’s implementation are pretty straightforward. The important part is the LoginController, below is the untestable default way of implementing LoginController.

LoginController – Classic

public class LoginController {

	private String username;
	private String password;
	private LoginService loginService;

	//JSF can set through a managed-property
	public void setLoginService(LoginService loginService) {
		this.loginService = loginService;
	}

	public String loginClicked() {
		boolean isValidUser = loginService.login(username, password);

		if(isValidUser) {
			return "mainpage";
		} else {
			//Evil code that makes your backing bean untestable
			FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Invalid login", "Wrong username/password combination");
			FacesContext.getCurrentInstance().add(null, message);
			//End of evil code

			return "failed";
		}
	}

	public String getUsername() {return username;}
	public void setUsername(String username) {this.username = username;}

	public String getPassword() {return password;}
	public void setPassword(String password) {this.password = password;}
}

The problem above is by using FacesContext to add messages, you just made your code hard to test. At this point you can use shale test static mock library(the project is dead already) or change your code to remove FacesContext references. Here’s how to;

LoginController – Better


@Controller(name="loginController", scope=Scope.REQUEST)
public class LoginController {

	private String username;
	private String password;
	private LoginService loginService;
	private FacesMessages messages;

	@Inject
	public LoginController(LoginService loginService) {
		this.loginService = loginService;
	}

	@Inject
	public void setFacesMessages(FacesMessages messages) {
		this.messages = messages;
	}

	public String loginClicked() {
		boolean isValidUser = loginService.login(username, password);

		if(isValidUser) {
			return "mainpage";
		} else {
			messages.addError("Invalid login", "Wrong username/password combination");

			return "failed";
		}
	}

	public String getUsername() {return username;}
	public void setUsername(String username) {this.username = username;}

	public String getPassword() {return password;}
	public void setPassword(String password) {this.password = password;}
}

That’s it, now you don’t reference FacesContext any more, FacesMessages is a simple interface, you can inject it with @Inject. Although you can use Field injection to avoid a setter for simplicity but that’s just a bad practice regarding testing.

Finally here’s a simple test with JUnit and Mockito(My favorite mock library).

LoginControllerTest

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.primefaces.examples.bookstore.service.LoginService;
import org.primefaces.examples.bookstore.view.LoginController;
import org.primefaces.optimus.application.FacesMessages;

public class LoginControllerTest {

	private LoginController controller;
	private LoginService loginService;
	private FacesMessages facesMessages;

	@Before
	public void setup() {
		loginService = mock(LoginService.class);
		facesMessages = mock(FacesMessages.class);

		controller = new LoginController(loginService);
		controller.setFacesMessages(facesMessages);
	}

	@After
	public void after() {
		controller = null;
	}

	@Test
	public void invalidCredentialsShouldStayOnLoginPageAndGiveMessage() {
		controller.setUsername("primo");
		controller.setPassword("1234");

		//stub
		when(loginService.login("primo", "1234")).thenReturn(false);

		//execute command action
		String result = controller.loginClicked();

		//outcome should be failed
		assertEquals("failed", result);

		//verify if message is added
		verify(facesMessages).addError("Invalid login", "Wrong username/password combination");
	}

	@Test
	public void correctCredentialsShouldLoginTheUser() {
		controller.setUsername("primo");
		controller.setPassword("4444");

		//stub
		when(loginService.login("primo", "4444")).thenReturn(true);

		//execute command action
		String result = controller.loginClicked();

		//outcome should be failed
		assertEquals("mainpage", result);
	}
}

In addition to FacesMessages, I’ve also added more solutions to commonly used stuff like Request Parameters or Session. Injection them is as easy as;


	@Inject
	private Params params;

	@Inject
	private Session session;

Whole idea is to abstract backing beans from any code(FacesContext) that makes testing harder and introduce more interfaces to code with.

5 Responses to Easy Unit Testing JSF Backing Beans

  1. Thanks a lot for the article. But will use it with TestNG as we have an integrated environment of Seam and Guice along with PrimeFaces and RichFaces (with RI Mojarra)

  2. fiorenzo says:

    Hi Cagatay,

    where’s org.primefaces.optimus.application.FacesMessages ??

    In optimus-0.8.0.jar in package org.primefaces.optimus.application i find only OptimusViewHandler.

    Thank’s in advance!

  3. Hey Fiorenzo, it’s added after 0.8.0 release and available in 0.9.0-SNAPSHOT.

  4. fiorenzo says:

    OPS!!!

%d bloggers like this: