diff --git a/api/tests/Storage/BaseTest.php b/api/tests/Storage/BaseTest.php
new file mode 100644
index 0000000000..267c6eefe1
--- /dev/null
+++ b/api/tests/Storage/BaseTest.php
@@ -0,0 +1,107 @@
+
+ * @copyright 2017RalfBecker@outdoor-training.de
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ */
+
+use PHPUnit\Framework\TestCase;
+use EGroupware\Api;
+
+class BaseTest extends TestCase
+{
+ /**
+ *
+ * @var Api\Db
+ */
+ private static $db;
+
+ /**
+ *
+ * @var Api\Storage\Base
+ */
+ private $storage;
+
+ public static function setUpBeforeClass()
+ {
+ if (ini_get('session.save_handler') == 'files' && !is_writable(ini_get('session.save_path')) && is_dir('/tmp') && is_writable('/tmp'))
+ {
+ ini_set('session.save_path','/tmp'); // regular users may have no rights to apache's session dir
+ }
+ $_REQUEST['domain'] = $GLOBALS['EGW_DOMAIN'];
+
+ $GLOBALS['egw_info'] = array(
+ 'flags' => array(
+ 'noheader' => True,
+ 'nonavbar' => True,
+ 'currentapp' => 'setup',
+ 'noapi' => true,
+ ));
+ require(__DIR__.'/../../../header.inc.php');
+
+ $GLOBALS['egw'] = new stdClass();
+ $GLOBALS['egw']->db = self::$db = new Api\Db($GLOBALS['egw_domain'][$GLOBALS['EGW_DOMAIN']]);
+ self::$db->connect();
+ }
+
+ protected function setUp()
+ {
+ $this->storage = new Api\Storage\Base('test', 'egw_test', self::$db);
+ }
+
+ protected function assertPreConditions()
+ {
+ $tables = self::$db->table_names(true);
+ $this->assertContains('egw_test', $tables);
+ }
+
+ public function testSaveInternalState()
+ {
+ $this->storage->data = $data = array(
+ 't_title' => 'Test',
+ 't_desc' => "First Line\nSecond Line\n\n...",
+ 't_start' => $start=Api\DateTime::to('now', 'ts'),
+ 't_end' => $end=Api\DateTime::to('now', 'ts'),
+ 't_modifier' => 123,
+ );
+ $this->storage->Save();
+ $this->assertGreaterThan(0, $this->storage->data['t_id']);
+
+ $row = self::$db->select('egw_test', '*', array('t_id' => $this->storage->data['t_id']),
+ __LINE__, __FILE__, false, '', 'test')->fetch();
+ $this->assertInternalType('array', $row);
+ $this->assertEquals($data['t_title'], $row['t_title']);
+ $this->assertEquals($data['t_desc'], $row['t_desc']);
+ $this->assertEquals($data['t_modifier'], $row['t_modifier']);
+ $this->assertEquals(Api\DateTime::user2server($start), $row['t_start']);
+ $this->assertEquals(Api\DateTime::user2server($end, Api\DateTime::DATABASE), $row['t_end']);
+ $this->assertEquals(new DateTime('now'), new DateTime($row['t_modified']), '', 1);
+
+ return $this->storage->data;
+ }
+
+ /**
+ *
+ * @param array $data
+ * @depends testSaveInternalState
+ */
+ public function testReadFromDb(array $data)
+ {
+ $read = $this->storage->read($data['t_id']);
+ // not set above, just be DB or read method
+ unset($read['t_modified'], $read['user_timezone_read']);
+ // set as ts, but read as is in DB
+ $data['t_end'] = Api\DateTime::to($data['t_end'], Api\DateTime::DATABASE);
+ $this->assertEquals($data, $read);
+ }
+
+ /*public function testSaveGivenState()
+ {
+
+ }*/
+}
diff --git a/api/tests/fixtures/apps/test/setup/setup.inc.php b/api/tests/fixtures/apps/test/setup/setup.inc.php
new file mode 100644
index 0000000000..cd61121892
--- /dev/null
+++ b/api/tests/fixtures/apps/test/setup/setup.inc.php
@@ -0,0 +1,37 @@
+
+ * @copyright 2017RalfBecker@outdoor-training.de
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ */
+
+$setup_info['test']['name'] = 'test';
+$setup_info['test']['version'] = '17.1.001';
+$setup_info['test']['app_order'] = 5;
+$setup_info['test']['tables'] = array('egw_test');
+$setup_info['test']['enable'] = 1;
+$setup_info['test']['index'] = 'timesheet.timesheet_ui.index&ajax=true';
+
+$setup_info['test']['author'] =
+$setup_info['test']['maintainer'] = array(
+ 'name' => 'Ralf Becker',
+ 'email' => 'rb@egroupware.org',
+);
+$setup_info['test']['license'] = 'GPL';
+$setup_info['test']['description'] = 'Testapp for phpUnit tests';
+$setup_info['test']['note'] = '';
+
+/* The hooks this app includes, needed for hooks registration */
+$setup_info['test']['hooks'] = array();
+
+/* Dependencies for this app to work */
+$setup_info['test']['depends'][] = array(
+ 'appname' => 'api',
+ 'versions' => Array('16.1')
+);
+
diff --git a/api/tests/fixtures/apps/test/setup/tables_current.inc.php b/api/tests/fixtures/apps/test/setup/tables_current.inc.php
new file mode 100644
index 0000000000..8352d641db
--- /dev/null
+++ b/api/tests/fixtures/apps/test/setup/tables_current.inc.php
@@ -0,0 +1,29 @@
+ array(
+ 'fd' => array(
+ 't_id' => array('type' => 'auto','nullable' => False),
+ 't_title' => array('type' => 'varchar','precision' => '80'),
+ 't_desc' => array('type' => 'varchar','precision' => '16000'),
+ 't_modifier' => array('type' => 'int','meta' => 'account','precision' => '4'),
+ 't_modified' => array('type' => 'timestamp','meta' => 'timestamp','default' => 'current_timestamp', 'nullable' => false),
+ 't_start' => array('type' => 'int','meta' => 'timestamp','precision' => '8'),
+ 't_end' => array('type' => 'timestamp','meta' => 'timestamp')
+ ),
+ 'pk' => array('t_id'),
+ 'fk' => array(),
+ 'ix' => array('t_modified'),
+ 'uc' => array()
+ )
+);
diff --git a/doc/phpunit.xml b/doc/phpunit.xml
index 296c03147c..55595438bd 100644
--- a/doc/phpunit.xml
+++ b/doc/phpunit.xml
@@ -14,6 +14,7 @@
../api/src/test/
../api/src/*/test/
+ ../api/tests
../api/src/test/EtemplateTest.php
@@ -23,10 +24,10 @@
../*/test/
-
../*/src/test/
../*/src/*/test/
+ ../*/tests/
../api