Tuesday, April 14, 2009

Using EasyMock's class extensions

This is the third part of a series of articles around building an App Engine application based on Java and Google Web Toolkit. If you are interested in part one, check out this post.

Here is another installment on my second iteration of the key manager. The iteration contains a lot more code than originally anticipated, so I have to split describing it up in a couple of posts.

For today, I chose to focus on another aspect of using EasyMock: class extensions. In my original design goals, I mentioned that data to the server should always be encrypted (no clear text passwords should arrive in App Engine). To make it easier to enforce this programatically, I came up with the following classes. First, I built an abstract Scrambler class that can make a String or a PasswordData object unreadable:

package com.appenginefan.schluesselmeister.client;

/**
* Contains logic to scramble/unscramble strings and
* PasswordData.
*/
public abstract class Scrambler {

protected String scramblingSecret = "thereIsNoSpoon";

public void setScramblingSecret(String secret) {
this.scramblingSecret = secret;
}

public abstract String scramble(String s);

public abstract String unscramble(String s);

public PasswordData scramble(PasswordData p) {
p = p.clone();
p.setCategory(scramble(p.getCategory()));
p.setDescription(scramble(p.getDescription()));
p.setKey(scramble(p.getKey()));
p.setPassword(scramble(p.getPassword()));
p.setUrl(scramble(p.getUrl()));
return p;
}

public PasswordData unscramble(PasswordData p) {
p = p.clone();
p.setCategory(unscramble(p.getCategory()));
p.setDescription(unscramble(p.getDescription()));
p.setKey(unscramble(p.getKey()));
p.setPassword(unscramble(p.getPassword()));
p.setUrl(unscramble(p.getUrl()));
return p;
}
}



Then, I built a Store class that would accept data in an encrypted fashion and release it in an unencrypted way for the UI:

package com.appenginefan.schluesselmeister.client;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Contains any data structure that reside in the memory of
* the browser.
*/
public class Store {

private final Scrambler scrambler;

private String currentCategoryScrambled = "";

private final List<String> listOfCategories =
new ArrayList<String>();

private final Map<String, List<PasswordData>> passwordCache =
new HashMap<String, List<PasswordData>>();

public Store(Scrambler scrambler) {
this.scrambler = scrambler;
}

public void updateCacheWithScrambledData(
String scrambledCategory,
List<PasswordData> scrambledData) {
passwordCache.put(scrambledCategory,
new ArrayList<PasswordData>(scrambledData));
}

public void updateListOfCategoriesWithScrambledData(
List<String> newScrambledList) {
listOfCategories.clear();
listOfCategories.addAll(newScrambledList);
}

public List<String> getListOfCategoriesUnscrambled() {
List<String> result = new ArrayList<String>();
for (String s : listOfCategories) {
result.add(scrambler.unscramble(s));
}
Collections.sort(result);
return result;
}

public List<PasswordData> getListOfPasswordDataUnscrambled(
String scrambledCategoryOrNull) {
final List<PasswordData> result =
new ArrayList<PasswordData>();
if (passwordCache.containsKey(scrambledCategoryOrNull)) {
for (PasswordData data : passwordCache
.get(scrambledCategoryOrNull)) {
result.add(scrambler.unscramble(data));
}
}
return result;
}

public String getCurrentCategoryScrambled() {
return currentCategoryScrambled;
}

public void setCurrentCategory(
String unscrambledCurrentCategory) {
if (unscrambledCurrentCategory != null) {
this.currentCategoryScrambled =
scrambler.scramble(unscrambledCurrentCategory);
} else {
this.currentCategoryScrambled = null;
}
}
}


Unlike in my previous article, I have chosen not to program against interfaces. Scrambler has abstract methods, but the code for encoding a PasswordData object is independent on the exact encryption algorithm. Store is a simple utility class, and wrapping it in an interface feels like overkill.

Despite not having interfaces, I still would like to use my mocking framework because:


  • I do not have to implement an encryption strategy, just to test with a Scrambler, and

  • I can easily restrict what methods should be called and with what parameters.



Lucky for me, EasyMock has a class extensions package. It uses cglib to create mock objects even for existing classes. The following unit tests makes use of this by creating a mock Scrambler:

package com.appenginefan.schluesselmeister.tests;

import java.util.Collections;

import org.easymock.classextension.EasyMock;

import com.appenginefan.schluesselmeister.client.PasswordData;
import com.appenginefan.schluesselmeister.client.Scrambler;
import com.appenginefan.schluesselmeister.client.Store;

import junit.framework.TestCase;

public class StoreTest
extends TestCase {

private Scrambler scrambler;

private Store store;

private PasswordData unscrambledData = new PasswordData();

private PasswordData scrambledData = new PasswordData();

private String scrambled = "foo";

private String unscrambled = "oof";

@Override
protected void setUp() throws Exception {
super.setUp();
scrambler = EasyMock.createMock(Scrambler.class);
store = new Store(scrambler);
unscrambledData.setCategory(unscrambled);
unscrambledData.setId(1L);
scrambledData.setCategory(scrambled);
scrambledData.setId(2L);
}

public void testUpdateData() {

// Nothing should get scrambled when data gets added
EasyMock.replay(scrambler);
store.updateCacheWithScrambledData(scrambledData
.getCategory(), Collections
.singletonList(scrambledData));
EasyMock.verify(scrambler);
EasyMock.reset(scrambler);

// When we get the data out, it should be unscrambled
EasyMock.expect(scrambler.unscramble(scrambledData))
.andReturn(unscrambledData);
EasyMock.replay(scrambler);
assertEquals(unscrambledData, store
.getListOfPasswordDataUnscrambled(scrambled).get(0));
EasyMock.verify(scrambler);
}

public void testUpdateCategories() {

// Nothing should get scrambled when data gets added
EasyMock.replay(scrambler);
store
.updateListOfCategoriesWithScrambledData(Collections
.singletonList(scrambled));
EasyMock.verify(scrambler);
EasyMock.reset(scrambler);

// When we get the data out, it should be unscrambled
EasyMock.expect(scrambler.unscramble(scrambled))
.andReturn(unscrambled);
EasyMock.replay(scrambler);
assertEquals(unscrambled, store
.getListOfCategoriesUnscrambled().get(0));
EasyMock.verify(scrambler);
}

public void testUpdateCurrentCategory() {

// The category should get scrambled the moment it gets
// added
EasyMock.expect(scrambler.scramble(unscrambled))
.andReturn(scrambled);
EasyMock.replay(scrambler);
store.setCurrentCategory(unscrambled);
EasyMock.verify(scrambler);
EasyMock.reset(scrambler);

// When we get the data out, it should be scrambled
EasyMock.replay(scrambler);
assertEquals(scrambled, store
.getCurrentCategoryScrambled());
EasyMock.verify(scrambler);
}
}


Mock frameworks like EasyMock make, well..., mocking easy ;-). To reiterate: all the unit tests I am showing here are written in Java but for GTW code. This means, the classes I am testing here will eventually be compiled to JavaScript and executed on the client -- yet I can still easily test them in Eclipse and integrate those tests into whatever build infrastructure (ant, continuous build, ...) I may have.

Last but not least...


A quick shoutout to a couple of interesting pages:

0 comments: