diff --git a/.gitignore b/.gitignore
index 5db5c40da457cb8918a337c4107ff14745bb16b2..c1d403d4ec237909d811bd9008089e65ae20bdfa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,5 +11,7 @@
 \#*\#
 *.mat
 *.asv
+tests/git.info
+tests/git.last-commit-hash
 
 /missing_dbnomics
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..af480e677af3715aed02419b1709b4d20ba2e3d5
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,11 @@
+variables:
+  GIT_SUBMODULE_STRATEGY: normal
+  TERM: linux
+
+before_script:
+  - git clone https://git.dynare.org/Dynare/m-unit-tests
+
+test_matlab:
+  stage: test
+  script:
+    - make check-matlab
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..926f34e1c6b265faaa8e9f8ea8ef6ae9cc047a25
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,15 @@
+OCTAVE ?= octave-cli
+MATLAB ?= $(shell which matlab)
+
+all: check-matlab
+
+m-unit-tests/src/mtest.m:
+	git clone https://git.dynare.org/Dynare/m-unit-tests
+
+check-matlab: m-unit-tests/src/mtest.m
+	@$(MATLAB)  -nosplash -nodisplay -r "addpath([pwd '/m-unit-tests/src']); cd tests; runalltests; quit" && [ ! -f ./tests/failed ] && [ -f ./tests/pass ]
+
+check-clean:
+	rm -f tests/*_test_*.m tests/*.csv tests/*.xls tests/*.xlsx tests/*.mat tests/failed tests/datafile_for_test
+	rm -f git.info git.last-commit-hash tests/pass tests/failed tests/*.spc
+	rm -rf m-unit-tests
diff --git a/tests/run_all_tests_matlab.m b/archive/run_all_tests_matlab.m
similarity index 100%
rename from tests/run_all_tests_matlab.m
rename to archive/run_all_tests_matlab.m
diff --git a/tests/runalltests.m b/tests/runalltests.m
new file mode 100644
index 0000000000000000000000000000000000000000..e0c15a229eda9c529b610b1312760e9876ac9365
--- /dev/null
+++ b/tests/runalltests.m
@@ -0,0 +1,63 @@
+function runalltests()
+
+% Copyright (C) 2020 Dynare Team
+%
+% This code is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as published by
+% the Free Software Foundation, either version 3 of the License, or
+% (at your option) any later version.
+%
+% Dynare dseries submodule is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with Dynare.  If not, see <http://www.gnu.org/licenses/>.
+
+opath = path();
+
+system('rm -f failed');
+system('rm -f pass');
+
+% Check that the m-unit-tests module is available.
+try
+    initialize_unit_tests_toolbox;
+catch
+    error('Missing dependency: m-unit-tests module is not available.')
+end
+
+% Get path to the current script
+unit_tests_root = strrep(which('runalltests'),'runalltests.m','');
+
+% Initialize the mdbnomics module
+try
+    initialize_mdbnomics();
+catch
+    addpath([unit_tests_root '../src']);
+    initialize_mdbnomics();
+end
+
+warning off
+
+if isoctave()
+    if ~user_has_octave_forge_package('io')
+        error('Missing dependency: io package is not available.')
+    end
+    more off;
+end
+
+r = run_unitary_tests_in_directory(unit_tests_root(1:end-1));
+
+delete('*.log');
+
+if any(~[r{:,3}])
+    system('touch failed');
+else
+    system('touch pass');
+end
+
+warning on
+path(opath);
+
+display_report(r);
diff --git a/tests/test_fetch_series_by_api_link.m b/tests/test_fetch_series_by_api_link.m
index 45e58257a4528d7920c5a5d5d0ea4f8470f18cda..9ba7cccfc337459441891eb9a5b292051dee570a 100644
--- a/tests/test_fetch_series_by_api_link.m
+++ b/tests/test_fetch_series_by_api_link.m
@@ -1,4 +1,4 @@
-function test_fetch_series_by_api_link()
+function test_fetch_series_by_api_link() % --*-- Unitary tests --*--
 df = fetch_series_by_api_link("https://api.db.nomics.world/v22/series/BIS/long_pp?limit=1000&offset=0&q=&observations=1&align_periods=1&dimensions=%7B%7D");
     
 idx = find(strcmp('provider_code',df(1,:)));
@@ -14,4 +14,15 @@ assert(dataset_codes{1} == "long_pp");
 idx = find(strcmp('series_code',df(1,:)));
 series_codes = unique(df(2:end,idx));
 assert(length(series_codes) > 1);
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_by_api_link();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_by_code.m b/tests/test_fetch_series_by_code.m
index 63865078980903ca17350f32fbd1755e30419499..cd91908df83474f9e9247052e2f39da968fc835f 100644
--- a/tests/test_fetch_series_by_code.m
+++ b/tests/test_fetch_series_by_code.m
@@ -1,4 +1,4 @@
-function test_fetch_series_by_code()
+function test_fetch_series_by_code() % --*-- Unitary tests --*--
 df = fetch_series('provider_code', "AMECO", 'dataset_code', "ZUTN", 'series_code',"EA19.1.0.0.0.ZUTN");
 
 idx = find(strcmp('provider_code',df(1,:)));
@@ -15,4 +15,15 @@ idx = find(strcmp('series_code',df(1,:)));
 series_codes = unique(df(2:end,idx));
 assert(length(series_codes) == 1);
 assert(series_codes{1} == "EA19.1.0.0.0.ZUTN");
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_by_code();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_by_code_mask.m b/tests/test_fetch_series_by_code_mask.m
index f2e6b895c56a6933fcc4536635d4be96aaceb9e3..55f59e8c541881ad1c64f338e2c679a9d985b9ca 100644
--- a/tests/test_fetch_series_by_code_mask.m
+++ b/tests/test_fetch_series_by_code_mask.m
@@ -1,4 +1,4 @@
-function test_fetch_series_by_code_mask()
+function test_fetch_series_by_code_mask() % --*-- Unitary tests --*--
 df = fetch_series('provider_code', "IMF", 'dataset_code', "CPI", 'series_code', "M.FR+DE.PCPIEC_IX+PCPIA_IX");
 
 idx = find(strcmp('provider_code',df(1,:)));
@@ -14,4 +14,15 @@ assert(dataset_codes{1} == "CPI");
 idx = find(strcmp('series_code',df(1,:)));
 series_codes = unique(df(2:end,idx));
 assert(length(series_codes) == 4);
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_by_code_mask();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_by_code_mask_with_plus_in_dimension_code.m b/tests/test_fetch_series_by_code_mask_with_plus.m
similarity index 71%
rename from tests/test_fetch_series_by_code_mask_with_plus_in_dimension_code.m
rename to tests/test_fetch_series_by_code_mask_with_plus.m
index 757de4f2c2e49b791f48eb27160df167f5485bdf..f426f8b95430fe8ffcad67ea6903a7be384094ec 100644
--- a/tests/test_fetch_series_by_code_mask_with_plus_in_dimension_code.m
+++ b/tests/test_fetch_series_by_code_mask_with_plus.m
@@ -1,4 +1,4 @@
-function test_fetch_series_by_code_mask_with_plus_in_dimension_code()
+function test_fetch_series_by_code_mask_with_plus() % --*-- Unitary tests --*--
 df = fetch_series('provider_code', "SCB", 'dataset_code', "AKIAM", 'series_code', '"J+K"+"G+H".AM0301C1');
 
 idx = find(strcmp('provider_code',df(1,:)));
@@ -16,4 +16,15 @@ series_codes = unique(df(2:end,idx));
 check = {'J+K.AM0301C1', 'G+H.AM0301C1'}';  
 logA = cell2mat(cellfun(@(c)strcmp(c,series_codes),check,'UniformOutput',false));
 assert(sum(logA)==2);
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_by_code_mask_with_plus();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
diff --git a/tests/test_fetch_series_by_dimension.m b/tests/test_fetch_series_by_dimension.m
index f30e8a15e8c119cac93af27c0505cd568ac148c1..a1afd9ee84cefc496504c83cb21fda449d104494 100644
--- a/tests/test_fetch_series_by_dimension.m
+++ b/tests/test_fetch_series_by_dimension.m
@@ -1,4 +1,4 @@
-function test_fetch_series_by_dimension()
+function test_fetch_series_by_dimension() % --*-- Unitary tests --*--
 df = fetch_series('provider_code',"WB",'dataset_code',"DB", 'dimensions', '{"country":["ES","FR","IT"],"indicator":["IC.REG.COST.PC.FE.ZS.DRFN"]}');
 
 idx = find(strcmp('provider_code',df(1,:)));
@@ -14,4 +14,15 @@ assert(dataset_codes{1} == "DB");
 idx = find(strcmp('series_code',df(1,:)));
 series_codes = unique(df(2:end,idx));
 assert(length(series_codes) == 3);
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_by_dimension();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_by_id.m b/tests/test_fetch_series_by_id.m
index a24eb700505071e0f0e59d1eded5709b16c45944..b74b42a0486ba81aec709bcd6c1257f8bba55910 100644
--- a/tests/test_fetch_series_by_id.m
+++ b/tests/test_fetch_series_by_id.m
@@ -1,4 +1,4 @@
-function test_fetch_series_by_id()
+function test_fetch_series_by_id() % --*-- Unitary tests --*--
 df = fetch_series('series_ids',"AMECO/ZUTN/EA19.1.0.0.0.ZUTN");
 
 idx = find(strcmp('provider_code',df(1,:)));
@@ -15,4 +15,15 @@ idx = find(strcmp('series_code',df(1,:)));
 series_codes = unique(df(2:end,idx));
 assert(length(series_codes) == 1);
 assert(series_codes{1} == "EA19.1.0.0.0.ZUTN");
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_by_id();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_by_ids_in_different_datasets.m b/tests/test_fetch_series_by_ids_in_different_datasets.m
index 9bc2103aeea610e1a52277994a2a199953d5e5c0..1f25d824e3fb2518c8af3eb907fca9f8750c0ffa 100644
--- a/tests/test_fetch_series_by_ids_in_different_datasets.m
+++ b/tests/test_fetch_series_by_ids_in_different_datasets.m
@@ -1,4 +1,4 @@
-function test_fetch_series_by_ids_in_different_datasets()
+function test_fetch_series_by_ids_in_different_datasets() % --*-- Unitary tests --*--
 df = fetch_series('series_ids', ["AMECO/ZUTN/EA19.1.0.0.0.ZUTN", "BIS/cbs/Q.S.5A.4B.F.B.A.A.LC1.A.1C"]);
 
 idx = find(strcmp('provider_code',df(1,:)));
@@ -18,4 +18,15 @@ series_codes = unique(df(2:end,idx));
 assert(length(series_codes) == 2);
 assert(series_codes{1} == "EA19.1.0.0.0.ZUTN");
 assert(series_codes{2} == "Q.S.5A.4B.F.B.A.A.LC1.A.1C");
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_by_ids_in_different_datasets();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_by_ids_in_same_dataset.m b/tests/test_fetch_series_by_ids_in_same_dataset.m
index 7924f0ba8e34c3f62e5aae5fc58e29856d6e6711..9be930a6597c86901918b2d3c93a7a43f14a3e87 100644
--- a/tests/test_fetch_series_by_ids_in_same_dataset.m
+++ b/tests/test_fetch_series_by_ids_in_same_dataset.m
@@ -1,4 +1,4 @@
-function test_fetch_series_by_ids_in_same_dataset()
+function test_fetch_series_by_ids_in_same_dataset() % --*-- Unitary tests --*--
 df = fetch_series('series_ids', ["AMECO/ZUTN/EA19.1.0.0.0.ZUTN",...
                                  "AMECO/ZUTN/DNK.1.0.0.0.ZUTN"]);
     
@@ -17,4 +17,15 @@ series_codes = unique(df(2:end,idx));
 assert(length(series_codes) == 2);
 assert(series_codes{1} == "DNK.1.0.0.0.ZUTN");
 assert(series_codes{2} == "EA19.1.0.0.0.ZUTN");
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_by_ids_in_same_dataset();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_of_dataset.m b/tests/test_fetch_series_of_dataset.m
index 379852624bd61d0c7126e3ac4db2b0d51e22f91c..4f86268471c8943e6a751bc39e7e9089415bea8e 100644
--- a/tests/test_fetch_series_of_dataset.m
+++ b/tests/test_fetch_series_of_dataset.m
@@ -1,4 +1,4 @@
-function test_fetch_series_of_dataset()
+function test_fetch_series_of_dataset() % --*-- Unitary tests --*--
 df = fetch_series('provider_code', "AMECO", 'dataset_code', "ZUTN");
 
 idx = find(strcmp('provider_code',df(1,:)));
@@ -14,4 +14,15 @@ assert(dataset_codes{1} == "ZUTN");
 idx = find(strcmp('series_code',df(1,:)));
 series_codes = unique(df(2:end,idx));
 assert(length(series_codes) > 1);
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_of_dataset();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_with_filter_on_one_series.m b/tests/test_fetch_series_with_filter_on_one_series.m
index 27dea0116bd15c2ce5166ac53a6ee0e1601a971f..596c24db03572a40de5c8a93fd0ae5a6ca17c0af 100644
--- a/tests/test_fetch_series_with_filter_on_one_series.m
+++ b/tests/test_fetch_series_with_filter_on_one_series.m
@@ -1,4 +1,4 @@
-function test_fetch_series_with_filter_on_one_series()
+function test_fetch_series_with_filter_on_one_series() % --*-- Unitary tests --*--
 filters_ = '[{"code": "interpolate", "parameters": {"frequency": "monthly", "method": "spline"}}]';
 df = fetch_series('provider_code', "AMECO", 'dataset_code', "ZUTN", 'series_code', "DEU.1.0.0.0.ZUTN", 'dbnomics_filters', filters_);
 
@@ -21,4 +21,15 @@ idx_freq = find(strcmp('x_frequency',df(1,:)));
 frequencies = unique(df(2:end, idx_freq));
 assert(any(strcmp(frequencies, "monthly")));
 assert(any(contains(series_codes, "_filtered")));
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_with_filter_on_one_series();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_with_max_nb_series.m b/tests/test_fetch_series_with_max_nb_series.m
index d09c92de53f74e8e2d4e491bd7be88d69457576a..5bae432822cd62fa62d61475f0ac3619af35dcf4 100644
--- a/tests/test_fetch_series_with_max_nb_series.m
+++ b/tests/test_fetch_series_with_max_nb_series.m
@@ -1,4 +1,4 @@
-function test_fetch_series_with_max_nb_series()
+function test_fetch_series_with_max_nb_series() % --*-- Unitary tests --*--
 df = fetch_series('provider_code', "AMECO", 'dataset_code', "ZUTN", 'max_nb_series',20);
 
 idx = find(strcmp('provider_code',df(1,:)));
@@ -14,4 +14,15 @@ assert(dataset_codes{1} == "ZUTN");
 idx = find(strcmp('series_code',df(1,:)));
 series_codes = unique(df(2:end,idx));
 assert(length(series_codes) <= 20);
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_with_max_nb_series();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file
diff --git a/tests/test_fetch_series_with_na_values.m b/tests/test_fetch_series_with_na_values.m
index 7195532028e6ef1d39e8565926af3332ba0fa85e..ae3b873e0425c9ba847ef36ce29b9fbffdbdae48 100644
--- a/tests/test_fetch_series_with_na_values.m
+++ b/tests/test_fetch_series_with_na_values.m
@@ -1,4 +1,4 @@
-function test_fetch_series_with_na_values()
+function test_fetch_series_with_na_values() % --*-- Unitary tests --*--
 df = fetch_series('provider_code', "AMECO", 'dataset_code', "ZUTN", 'series_code', "DEU.1.0.0.0.ZUTN");
 
 idx = find(strcmp('provider_code',df(1,:)));
@@ -20,4 +20,15 @@ assert(any(strcmp('NA', df(2:end,idx))) == true);
 
 idx = find(strcmp('value',df(1,:)));
 assert(any(isnan(cell2mat(df(2:end,idx)))) == true);
-end
\ No newline at end of file
+end
+
+%@test:1
+%$ try
+%$    test_fetch_series_with_na_values();
+%$    t(1) = true;
+%$ catch
+%$    t(1) = false;
+%$ end
+%$
+%$ T = t(1);
+%@eof:1
\ No newline at end of file