diff --git a/tests/unit/framework/console/controllers/MessageControllerTest.php b/tests/unit/framework/console/controllers/MessageControllerTest.php
new file mode 100644
index 0000000..82bb393
--- /dev/null
+++ b/tests/unit/framework/console/controllers/MessageControllerTest.php
@@ -0,0 +1,364 @@
+<?php
+
+use yiiunit\TestCase;
+use yii\console\controllers\MessageController;
+
+/**
+ * Unit test for [[\yii\console\controllers\MessageController]].
+ * @see MessageController
+ */
+class MessageControllerTest extends TestCase
+{
+	protected $sourcePath = '';
+	protected $messagePath = '';
+	protected $configFileName = '';
+
+	public function setUp()
+	{
+		$this->sourcePath = Yii::getAlias('@yiiunit/runtime/test_source');
+		$this->createDir($this->sourcePath);
+		$this->messagePath = Yii::getAlias('@yiiunit/runtime/test_messages');
+		$this->createDir($this->messagePath);
+		$this->configFileName = Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . 'message_controller_test_config.php';
+	}
+
+	public function tearDown()
+	{
+		$this->removeDir($this->sourcePath);
+		$this->removeDir($this->messagePath);
+		if (file_exists($this->configFileName)) {
+			unlink($this->configFileName);
+		}
+	}
+
+	/**
+	 * Creates directory.
+	 * @param $dirName directory full name
+	 */
+	protected function createDir($dirName)
+	{
+		if (!file_exists($dirName)) {
+			mkdir($dirName, 0777, true);
+		}
+	}
+
+	/**
+	 * Removes directory.
+	 * @param $dirName directory full name
+	 */
+	protected function removeDir($dirName)
+	{
+		if (!empty($dirName) && file_exists($dirName)) {
+			$this->removeFileSystemObject($dirName);
+		}
+	}
+
+	/**
+	 * Removes file system object: directory or file.
+	 * @param string $fileSystemObjectFullName file system object full name.
+	 */
+	protected function removeFileSystemObject($fileSystemObjectFullName)
+	{
+		if (!is_dir($fileSystemObjectFullName)) {
+			unlink($fileSystemObjectFullName);
+		} else {
+			$dirHandle = opendir($fileSystemObjectFullName);
+			while (($fileSystemObjectName = readdir($dirHandle)) !== false) {
+				if ($fileSystemObjectName==='.' || $fileSystemObjectName==='..') {
+					continue;
+				}
+				$this->removeFileSystemObject($fileSystemObjectFullName . DIRECTORY_SEPARATOR . $fileSystemObjectName);
+			}
+			closedir($dirHandle);
+			rmdir($fileSystemObjectFullName);
+		}
+	}
+
+	/**
+	 * Creates test message controller instance.
+	 * @return MessageController message command instance.
+	 */
+	protected function createMessageController()
+	{
+		$module = $this->getMock('yii\\base\\Module', array('fake'), array('console'));
+		$command = new MessageController('message', $module);
+		return $command;
+	}
+
+	/**
+	 * Emulates running of the message controller action.
+	 * @param string $actionId id of action to be run.
+	 * @param array $args action arguments.
+	 * @return string command output.
+	 */
+	protected function runMessageControllerAction($actionId, array $args=array())
+	{
+		$controller = $this->createMessageController();
+		ob_start();
+		ob_implicit_flush(false);
+		$params = array(
+			\yii\console\Request::ANONYMOUS_PARAMS => $args
+		);
+		$controller->run($actionId, $params);
+		return ob_get_clean();
+	}
+
+	/**
+	 * Creates message command config file at {@link configFileName}
+	 * @param array $config message command config.
+	 */
+	protected function composeConfigFile(array $config)
+	{
+		if (file_exists($this->configFileName)) {
+			unlink($this->configFileName);
+		}
+		$fileContent = '<?php return ' . var_export($config, true) . ';';
+		file_put_contents($this->configFileName, $fileContent);
+	}
+
+	/**
+	 * Creates source file with given content
+	 * @param string $content file content
+	 * @param string|null $name file self name
+	 */
+	protected function createSourceFile($content, $name = null)
+	{
+		if (empty($name)) {
+			$name = md5(uniqid()) . '.php';
+		}
+		file_put_contents($this->sourcePath . DIRECTORY_SEPARATOR . $name, $content);
+	}
+
+	/**
+	 * Creates message file with given messages.
+	 * @param string $name file name
+	 * @param array $messages messages.
+	 */
+	protected function createMessageFile($name, array $messages = array())
+	{
+		$fileName = $this->messagePath . DIRECTORY_SEPARATOR . $name;
+		if (file_exists($fileName)) {
+			unlink($fileName);
+		} else {
+			$dirName = dirname($fileName);
+			if (!file_exists($dirName)) {
+				mkdir($dirName, 0777, true);
+			}
+		}
+		$fileContent = '<?php return ' . var_export($messages, true) . ';';
+		file_put_contents($fileName, $fileContent);
+	}
+
+	// Tests:
+
+	public function testEmptyArgs()
+	{
+		$this->setExpectedException('CException', 'usageError');
+		$this->runMessageControllerAction('index', array());
+	}
+
+	public function testConfigFileNotExist()
+	{
+		$this->setExpectedException('CException', 'usageError');
+		$this->runMessageControllerAction('index', array('not_existing_file.php'));
+	}
+
+	public function testCreateTranslation()
+	{
+		$language = 'en';
+
+		$category = 'test_category';
+		$message = 'test message';
+		$sourceFileContent = "Yii::t('{$category}','{$message}')";
+		$this->createSourceFile($sourceFileContent);
+
+		$this->composeConfigFile(array(
+			'languages' => array($language),
+			'sourcePath' => $this->sourcePath,
+			'messagePath' => $this->messagePath,
+		));
+		$this->runMessageControllerAction('index', array($this->configFileName));
+
+		$this->assertTrue(file_exists($this->messagePath . DIRECTORY_SEPARATOR . $language), 'No language dir created!');
+		$messageFileName = $this->messagePath . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $category . '.php';
+		$this->assertTrue(file_exists($messageFileName), 'No message file created!');
+		$messages = require($messageFileName);
+		$this->assertTrue(is_array($messages), 'Unable to compose messages!');
+		$this->assertTrue(array_key_exists($message, $messages), 'Source message is missing!');
+	}
+
+	/**
+	 * @depends testCreateTranslation
+	 */
+	public function testNothingNew()
+	{
+		$language = 'en';
+
+		$category = 'test_category';
+		$message = 'test message';
+		$sourceFileContent = "Yii::t('{$category}', '{$message}')";
+		$this->createSourceFile($sourceFileContent);
+
+		$this->composeConfigFile(array(
+			'languages' => array($language),
+			'sourcePath' => $this->sourcePath,
+			'messagePath' => $this->messagePath,
+		));
+		$this->runMessageControllerAction('index', array($this->configFileName));
+
+		$messageFileName = $this->messagePath . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $category . '.php';
+
+		// check file not overwritten:
+		$messageFileContent = file_get_contents($messageFileName);
+		$messageFileContent .= '// some not generated by command content';
+		file_put_contents($messageFileName, $messageFileContent);
+
+		$this->runMessageControllerAction('index', array($this->configFileName));
+
+		$this->assertEquals($messageFileContent, file_get_contents($messageFileName));
+	}
+
+	/**
+	 * @depends testCreateTranslation
+	 */
+	public function testMerge()
+	{
+		$language = 'en';
+		$category = 'test_category';
+		$messageFileName = $language . DIRECTORY_SEPARATOR . $category . '.php';
+
+		$existingMessage = 'test existing message';
+		$existingMessageContent = 'test existing message content';
+		$this->createMessageFile($messageFileName, array(
+			$existingMessage => $existingMessageContent
+		));
+
+		$newMessage = 'test new message';
+		$sourceFileContent = "Yii::t('{$category}', '{$existingMessage}')";
+		$sourceFileContent .= "Yii::t('{$category}', '{$newMessage}')";
+		$this->createSourceFile($sourceFileContent);
+
+		$this->composeConfigFile(array(
+			'languages' => array($language),
+			'sourcePath' => $this->sourcePath,
+			'messagePath' => $this->messagePath,
+			'overwrite' => true,
+		));
+		$this->runMessageControllerAction('index', array($this->configFileName));
+
+		$messages = require($this->messagePath . DIRECTORY_SEPARATOR . $messageFileName);
+		$this->assertTrue(array_key_exists($newMessage, $messages), 'Unable to add new message!');
+		$this->assertTrue(array_key_exists($existingMessage, $messages), 'Unable to keep existing message!');
+		$this->assertEquals('', $messages[$newMessage], 'Wrong new message content!');
+		$this->assertEquals($existingMessageContent, $messages[$existingMessage], 'Unable to keep existing message content!');
+	}
+
+	/**
+	 * @depends testMerge
+	 */
+	public function testNoLongerNeedTranslation()
+	{
+		$language = 'en';
+		$category = 'test_category';
+		$messageFileName = $language . DIRECTORY_SEPARATOR . $category . '.php';
+
+		$oldMessage = 'test old message';
+		$oldMessageContent = 'test old message content';
+		$this->createMessageFile($messageFileName, array(
+			$oldMessage => $oldMessageContent
+		));
+
+		$sourceFileContent = "Yii::t('{$category}', 'some new message')";
+		$this->createSourceFile($sourceFileContent);
+
+		$this->composeConfigFile(array(
+			'languages' => array($language),
+			'sourcePath' => $this->sourcePath,
+			'messagePath' => $this->messagePath,
+			'overwrite' => true,
+			'removeOld' => false,
+		));
+		$this->runMessageControllerAction('index', array($this->configFileName));
+
+		$messages = require($this->messagePath . DIRECTORY_SEPARATOR . $messageFileName);
+
+		$this->assertTrue(array_key_exists($oldMessage, $messages), 'No longer needed message removed!');
+		$this->assertEquals('@@' . $oldMessageContent . '@@', $messages[$oldMessage], 'No longer needed message content does not marked properly!');
+	}
+
+	/**
+	 * @depends testMerge
+	 */
+	public function testMergeWithContentZero()
+	{
+		$language = 'en';
+		$category = 'test_category';
+		$messageFileName = $language . DIRECTORY_SEPARATOR . $category . '.php';
+
+		$zeroMessage = 'test zero message';
+		$zeroMessageContent = '0';
+		$falseMessage = 'test false message';
+		$falseMessageContent = 'false';
+		$this->createMessageFile($messageFileName, array(
+			$zeroMessage => $zeroMessageContent,
+			$falseMessage => $falseMessageContent,
+		));
+
+		$newMessage = 'test new message';
+		$sourceFileContent = "Yii::t('{$category}', '{$zeroMessage}')";
+		$sourceFileContent .= "Yii::t('{$category}', '{$falseMessage}')";
+		$sourceFileContent .= "Yii::t('{$category}', '{$newMessage}')";
+		$this->createSourceFile($sourceFileContent);
+
+		$this->composeConfigFile(array(
+			'languages' => array($language),
+			'sourcePath' => $this->sourcePath,
+			'messagePath' => $this->messagePath,
+			'overwrite' => true,
+		));
+		$this->runMessageControllerAction('index', array($this->configFileName));
+
+		$messages = require($this->messagePath . DIRECTORY_SEPARATOR . $messageFileName);
+		$this->assertTrue($zeroMessageContent === $messages[$zeroMessage], 'Message content "0" is lost!');
+		$this->assertTrue($falseMessageContent === $messages[$falseMessage], 'Message content "false" is lost!');
+	}
+
+	/**
+	 * @depends testCreateTranslation
+	 */
+	public function testMultiplyTranslators()
+	{
+		$language = 'en';
+		$category = 'test_category';
+
+		$translators = array(
+			'Yii::t',
+			'Custom::translate',
+		);
+
+		$sourceMessages = array(
+			'first message',
+			'second message',
+		);
+		$sourceFileContent = '';
+		foreach ($sourceMessages as $key => $message) {
+			$sourceFileContent .= $translators[$key] . "('{$category}', '{$message}');\n";
+		}
+		$this->createSourceFile($sourceFileContent);
+
+		$this->composeConfigFile(array(
+			'languages' => array($language),
+			'sourcePath' => $this->sourcePath,
+			'messagePath' => $this->messagePath,
+			'translator' => $translators,
+		));
+		$this->runMessageControllerAction('index', array($this->configFileName));
+
+		$messageFileName = $this->messagePath . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . $category . '.php';
+		$messages = require($messageFileName);
+
+		foreach ($sourceMessages as $sourceMessage) {
+			$this->assertTrue(array_key_exists($sourceMessage, $messages));
+		}
+	}
+}