diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..45449b7627a891a54ea7c95c399ce4a93f36d350
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.expected;
+
+import java.util.Arrays;
+
+/**
+ * This class builds a keyboard that is a two dimensional array of elements <code>E</code>.
+ *
+ * A keyboard consists of array of rows, and a row consists of array of elements. Each row may have
+ * different number of elements. A element of a keyboard can be specified by a row number and a
+ * column number, both numbers starts from 1.
+ *
+ * @param <E> the type of a keyboard element. A keyboard element must be an immutable object.
+ */
+abstract class AbstractKeyboardBuilder<E> {
+    // A building array of rows.
+    private final E[][] mRows;
+
+    // Returns an instance of default element.
+    abstract E defaultElement();
+    // Returns an <code>E</code> array instance of the <code>size</code>.
+    abstract E[] newArray(final int size);
+    // Returns an <code>E[]</code> array instance of the <code>size</code>.
+    abstract E[][] newArrayOfArray(final int size);
+
+    /**
+     * Construct a builder filled with the default element.
+     * @param dimensions the integer array of each row's size.
+     */
+    AbstractKeyboardBuilder(final int ... dimensions) {
+        mRows = newArrayOfArray(dimensions.length);
+        for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) {
+            mRows[rowIndex] = newArray(dimensions[rowIndex]);
+            Arrays.fill(mRows[rowIndex], defaultElement());
+        }
+    }
+
+    /**
+     * Construct a builder from template keyboard. This builder has the same dimensions and
+     * elements of <code>rows</rows>.
+     * @param rows the template keyboard rows. The elements of the <code>rows</code> will be
+     *        shared with this builder. Therefore a element must be an immutable object.
+     */
+    AbstractKeyboardBuilder(final E[][] rows) {
+        mRows = newArrayOfArray(rows.length);
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            final E[] row = rows[rowIndex];
+            mRows[rowIndex] = Arrays.copyOf(row, row.length);
+        }
+    }
+
+    /**
+     * Return current constructing keyboard.
+     * @return the array of the array of the element being constructed.
+     */
+    E[][] build() {
+        return mRows;
+    }
+
+    /**
+     * Get the current contents of the specified row.
+     * @param row the row number to get the contents.
+     * @return the array of elements at row number <code>row</code>.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    E[] getRowAt(final int row) {
+        final int rowIndex = row - 1;
+        if (rowIndex < 0 || rowIndex >= mRows.length) {
+            throw new RuntimeException("Illegal row number: " + row);
+        }
+        return mRows[rowIndex];
+    }
+
+    /**
+     * Set an array of elements to the specified row.
+     * @param row the row number to set <code>elements</code>.
+     * @param elements the array of elements to set at row number <code>row</code>.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    void setRowAt(final int row, final E[] elements) {
+        final int rowIndex = row - 1;
+        if (rowIndex < 0 || rowIndex >= mRows.length) {
+            throw new RuntimeException("Illegal row number: " + row);
+        }
+        mRows[rowIndex] = elements;
+    }
+
+    /**
+     * Set or insert an element at specified position.
+     * @param row the row number to set or insert the <code>element</code>.
+     * @param column the column number to set or insert the <code>element</code>.
+     * @param element the element to set or insert at <code>row,column</code>.
+     * @param insert if true, the <code>element</code> is inserted at <code>row,column</code>.
+     *        Otherwise the <code>element</code> replace the element at <code>row,column</code>.
+     * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal.
+     */
+    void setElementAt(final int row, final int column, final E element, final boolean insert) {
+        final E[] elements = getRowAt(row);
+        final int columnIndex = column - 1;
+        if (insert) {
+            if (columnIndex < 0 || columnIndex >= elements.length + 1) {
+                throw new RuntimeException("Illegal column number: " + column);
+            }
+            final E[] newElements = Arrays.copyOf(elements, elements.length + 1);
+            // Shift the remaining elements.
+            System.arraycopy(newElements, columnIndex, newElements, columnIndex + 1,
+                    elements.length - columnIndex);
+            // Insert the element at <code>row,column</code>.
+            newElements[columnIndex] = element;
+            // Replace the current row with one.
+            setRowAt(row, newElements);
+            return;
+        }
+        if (columnIndex < 0 || columnIndex >= elements.length) {
+            throw new RuntimeException("Illegal column number: " + column);
+        }
+        elements[columnIndex] = element;
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..61288f048f305a6a8b7d7422f29ea2c6aaf0797e
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.layout.expected;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * This class builds an expected keyboard for unit test.
+ */
+public final class ExpectedKeyboardBuilder extends AbstractKeyboardBuilder<ExpectedKey> {
+    public ExpectedKeyboardBuilder(final int ... dimensions) {
+        super(dimensions);
+    }
+
+    public ExpectedKeyboardBuilder(final ExpectedKey[][] rows) {
+        super(rows);
+    }
+
+    @Override
+    protected ExpectedKey defaultElement() {
+        return ExpectedKey.EMPTY_KEY;
+    }
+
+    @Override
+    ExpectedKey[] newArray(final int size) {
+        return new ExpectedKey[size];
+    }
+
+    @Override
+    ExpectedKey[][] newArrayOfArray(final int size) {
+        return new ExpectedKey[size][];
+    }
+
+    @Override
+    public ExpectedKey[][] build() {
+        return super.build();
+    }
+
+    // A replacement job to be performed.
+    interface ReplaceJob {
+        // Returns a {@link ExpectedKey} object to replace.
+        ExpectedKey replace(final ExpectedKey oldKey);
+        // Return true if replacing should be stopped at first occurrence.
+        boolean stopAtFirstOccurrence();
+    }
+
+    // Replace key(s) that has the specified visual.
+    private void replaceKeyOf(final ExpectedKeyVisual visual, final ReplaceJob job) {
+        int replacedCount = 0;
+        final ExpectedKey[][] rows = build();
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            final ExpectedKey[] keys = rows[rowIndex];
+            for (int columnIndex = 0; columnIndex < keys.length; columnIndex++) {
+                if (keys[columnIndex].getVisual().equalsTo(visual)) {
+                    keys[columnIndex] = job.replace(keys[columnIndex]);
+                    replacedCount++;
+                    if (job.stopAtFirstOccurrence()) {
+                        return;
+                    }
+                }
+            }
+        }
+        if (replacedCount == 0) {
+            throw new RuntimeException(
+                    "Can't find key that has visual: " + visual + " in\n" + toString(rows));
+        }
+    }
+
+    /**
+     * Set the row with specified keys that have specified labels.
+     * @param row the row number to set keys.
+     * @param labels the label texts of the keys.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setLabelsOfRow(final int row, final String ... labels) {
+        final ExpectedKey[] keys = new ExpectedKey[labels.length];
+        for (int columnIndex = 0; columnIndex < labels.length; columnIndex++) {
+            keys[columnIndex] = ExpectedKey.newInstance(labels[columnIndex]);
+        }
+        setRowAt(row, keys);
+        return this;
+    }
+
+    /**
+     * Set the "more keys" of the key that has the specified label.
+     * @param label the label of the key to set the "more keys".
+     * @param moreKeys the array of labels of the "more keys" to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setMoreKeysOf(final String label, final String ... moreKeys) {
+        final ExpectedKey[] expectedMoreKeys = new ExpectedKey[moreKeys.length];
+        for (int index = 0; index < moreKeys.length; index++) {
+            expectedMoreKeys[index] = ExpectedKey.newInstance(moreKeys[index]);
+        }
+        setMoreKeysOf(label, expectedMoreKeys);
+        return this;
+    }
+
+    /**
+     * Set the "more keys" of the key that has the specified label.
+     * @param label the label of the key to set the "more keys".
+     * @param moreKeys the array of "more key" to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setMoreKeysOf(final String label,
+            final ExpectedKey ... moreKeys) {
+        setMoreKeysOf(ExpectedKeyVisual.newInstance(label), moreKeys);
+        return this;
+    }
+
+    /**
+     * Set the "more keys" of the key that has the specified icon.
+     * @param iconId the icon id of the key to set the "more keys".
+     * @param moreKeys the array of "more key" to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder setMoreKeysOf(final int iconId, final ExpectedKey ... moreKeys) {
+        setMoreKeysOf(ExpectedKeyVisual.newInstance(iconId), moreKeys);
+        return this;
+    }
+
+    private void setMoreKeysOf(final ExpectedKeyVisual visual, final ExpectedKey[] moreKeys) {
+        replaceKeyOf(visual, new ReplaceJob() {
+            @Override
+            public ExpectedKey replace(final ExpectedKey oldKey) {
+                return ExpectedKey.newInstance(oldKey.getVisual(), oldKey.getOutput(), moreKeys);
+            }
+            @Override
+            public boolean stopAtFirstOccurrence() {
+                return true;
+            }
+        });
+    }
+
+    /**
+     * Insert the keys at specified position.
+     * @param row the row number to insert the <code>keys</code>.
+     * @param column the column number to insert the <code>keys</code>.
+     * @param keys the array of keys to insert at <code>row,column</code>.
+     * @return this builder.
+     * @throws {@link RuntimeException} if <code>row</code> or <code>column</code> is illegal.
+     */
+    public ExpectedKeyboardBuilder insertKeysAtRow(final int row, final int column,
+            final ExpectedKey ... keys) {
+        for (int index = 0; index < keys.length; index++) {
+            setElementAt(row, column + index, keys[index], true /* insert */);
+        }
+        return this;
+    }
+
+    /**
+     * Add the keys on the left most of the row.
+     * @param row the row number to add the <code>keys</code>.
+     * @param keys the array of keys to add on the left most of the row.
+     * @return this builder.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    public ExpectedKeyboardBuilder addKeysOnTheLeftOfRow(final int row,
+            final ExpectedKey ... keys) {
+        // Keys should be inserted from the last to preserve the order.
+        for (int index = keys.length - 1; index >= 0; index--) {
+            setElementAt(row, 1, keys[index], true /* insert */);
+        }
+        return this;
+    }
+
+    /**
+     * Add the keys on the right most of the row.
+     * @param row the row number to add the <code>keys</code>.
+     * @param keys the array of keys to add on the right most of the row.
+     * @return this builder.
+     * @throws {@link RuntimeException} if <code>row</code> is illegal.
+     */
+    public ExpectedKeyboardBuilder addKeysOnTheRightOfRow(final int row,
+            final ExpectedKey ... keys) {
+        final int rightEnd = getRowAt(row).length + 1;
+        insertKeysAtRow(row, rightEnd, keys);
+        return this;
+    }
+
+    /**
+     * Replace the most top-left key that has the specified label with the new key.
+     * @param label the label of the key to set <code>newKey</code>.
+     * @param newKey the key to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder replaceKeyOfLabel(final String label, final ExpectedKey newKey) {
+        final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label);
+        replaceKeyOf(visual, new ReplaceJob() {
+            @Override
+            public ExpectedKey replace(final ExpectedKey oldKey) {
+                return newKey;
+            }
+            @Override
+            public boolean stopAtFirstOccurrence() {
+                return true;
+            }
+        });
+        return this;
+    }
+
+    /**
+     * Replace the all specified keys  with the new key.
+     * @param key the key to be replaced by <code>newKey</code>.
+     * @param newKey the key to be set.
+     * @return this builder.
+     */
+    public ExpectedKeyboardBuilder replaceKeyOfAll(final ExpectedKey key,
+            final ExpectedKey newKey) {
+        replaceKeyOf(key.getVisual(), new ReplaceJob() {
+            @Override
+            public ExpectedKey replace(final ExpectedKey oldKey) {
+                return newKey;
+            }
+            @Override
+            public boolean stopAtFirstOccurrence() {
+                return false;
+            }
+        });
+        return this;
+    }
+
+    /**
+     * Returns new keyboard instance that has upper case keys of the specified keyboard.
+     * @param rows the lower case keyboard.
+     * @param locale the locale used to convert cases.
+     * @return the upper case keyboard.
+     */
+    public static ExpectedKey[][] toUpperCase(final ExpectedKey[][] rows, final Locale locale) {
+        final ExpectedKey[][] upperCaseRows = new ExpectedKey[rows.length][];
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            final ExpectedKey[] lowerCaseKeys = rows[rowIndex];
+            final ExpectedKey[] upperCaseKeys = new ExpectedKey[lowerCaseKeys.length];
+            for (int columnIndex = 0; columnIndex < lowerCaseKeys.length; columnIndex++) {
+                upperCaseKeys[columnIndex] = lowerCaseKeys[columnIndex].toUpperCase(locale);
+            }
+            upperCaseRows[rowIndex] = upperCaseKeys;
+        }
+        return upperCaseRows;
+    }
+
+    /**
+     * Convert the keyboard to human readable string.
+     * @param rows the keyboard to be converted to string.
+     * @return the human readable representation of <code>rows</code>.
+     */
+    public static String toString(final ExpectedKey[][] rows) {
+        final StringBuilder sb = new StringBuilder();
+        for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+            if (rowIndex > 0) {
+                sb.append("\n");
+            }
+            sb.append(Arrays.toString(rows[rowIndex]));
+        }
+        return sb.toString();
+    }
+}