diff --git a/lib/config/presets/github/__snapshots__/index.spec.ts.snap b/lib/config/presets/github/__snapshots__/index.spec.ts.snap
index 1bb03714ea7dc069c46529cec96dc272524d52e7..bf489876d85f2685c80d164d251a4c731bbfa06b 100644
--- a/lib/config/presets/github/__snapshots__/index.spec.ts.snap
+++ b/lib/config/presets/github/__snapshots__/index.spec.ts.snap
@@ -86,7 +86,7 @@ Array [
 ]
 `;
 
-exports[`config/presets/github/index getPreset() should return undefined 1`] = `
+exports[`config/presets/github/index getPreset() should throws not-found 1`] = `
 Array [
   Object {
     "headers": Object {
diff --git a/lib/config/presets/github/index.spec.ts b/lib/config/presets/github/index.spec.ts
index bf812a2bab7457df1c9d6b067fcfca57e92e6acd..23186b8dd65d74d3c8439011573c78ab5824debe 100644
--- a/lib/config/presets/github/index.spec.ts
+++ b/lib/config/presets/github/index.spec.ts
@@ -1,13 +1,14 @@
 import * as httpMock from '../../../../test/httpMock';
 import { getName, mocked } from '../../../../test/util';
 import * as _hostRules from '../../../util/host-rules';
+import { PRESET_NOT_FOUND } from '../util';
 import * as github from '.';
 
 jest.mock('../../../util/host-rules');
 
 const hostRules = mocked(_hostRules);
 
-const githubApiHost = 'https://api.github.com/';
+const githubApiHost = github.Endpoint;
 const basePath = '/repos/some/repo/contents';
 
 describe(getName(__filename), () => {
@@ -30,7 +31,7 @@ describe(getName(__filename), () => {
       const res = await github.fetchJSONFile(
         'some/repo',
         'some-filename.json',
-        'https://api.github.com/'
+        githubApiHost
       );
       expect(res).toMatchSnapshot();
       expect(httpMock.getTrace()).toMatchSnapshot();
@@ -145,7 +146,7 @@ describe(getName(__filename), () => {
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
 
-    it('should return undefined', async () => {
+    it('should throws not-found', async () => {
       httpMock
         .scope(githubApiHost)
         .get(`${basePath}/somefile.json`)
@@ -155,11 +156,12 @@ describe(getName(__filename), () => {
 
       try {
         global.appMode = true;
-        const content = await github.getPreset({
-          packageName: 'some/repo',
-          presetName: 'somefile/somename/somesubname',
-        });
-        expect(content).toBeUndefined();
+        await expect(
+          github.getPreset({
+            packageName: 'some/repo',
+            presetName: 'somefile/somename/somesubname',
+          })
+        ).rejects.toThrow(PRESET_NOT_FOUND);
       } finally {
         delete global.appMode;
       }
diff --git a/lib/config/presets/github/index.ts b/lib/config/presets/github/index.ts
index 2e40d93f9965df79ab2c5fafac80faecfc8711d6..7363c3810dce02efea758a23140b9058f94f3c53 100644
--- a/lib/config/presets/github/index.ts
+++ b/lib/config/presets/github/index.ts
@@ -2,8 +2,10 @@ import { PLATFORM_FAILURE } from '../../../constants/error-messages';
 import { PLATFORM_TYPE_GITHUB } from '../../../constants/platforms';
 import { logger } from '../../../logger';
 import { Http, HttpOptions } from '../../../util/http';
-import { ensureTrailingSlash } from '../../../util/url';
 import { Preset, PresetConfig } from '../common';
+import { PRESET_DEP_NOT_FOUND, fetchPreset } from '../util';
+
+export const Endpoint = 'https://api.github.com/';
 
 const http = new Http(PLATFORM_TYPE_GITHUB);
 
@@ -32,7 +34,7 @@ export async function fetchJSONFile(
       { statusCode: err.statusCode },
       `Failed to retrieve ${fileName} from repo`
     );
-    throw new Error('dep not found');
+    throw new Error(PRESET_DEP_NOT_FOUND);
   }
   try {
     const content = Buffer.from(res.body.content, 'base64').toString();
@@ -46,39 +48,19 @@ export async function fetchJSONFile(
 export async function getPresetFromEndpoint(
   pkgName: string,
   filePreset: string,
-  endpoint = 'https://api.github.com/'
+  endpoint = Endpoint
 ): Promise<Preset> {
-  // eslint-disable-next-line no-param-reassign
-  endpoint = ensureTrailingSlash(endpoint);
-  const [fileName, presetName, subPresetName] = filePreset.split('/');
-  let jsonContent: any;
-  if (fileName === 'default') {
-    try {
-      jsonContent = await fetchJSONFile(pkgName, 'default.json', endpoint);
-    } catch (err) {
-      if (err.message !== 'dep not found') {
-        throw err;
-      }
-      logger.debug('default.json preset not found - trying renovate.json');
-      jsonContent = await fetchJSONFile(pkgName, 'renovate.json', endpoint);
-    }
-  } else {
-    jsonContent = await fetchJSONFile(pkgName, `${fileName}.json`, endpoint);
-  }
-  if (presetName) {
-    if (subPresetName) {
-      return jsonContent[presetName]
-        ? jsonContent[presetName][subPresetName]
-        : undefined;
-    }
-    return jsonContent[presetName];
-  }
-  return jsonContent;
+  return fetchPreset({
+    pkgName,
+    filePreset,
+    endpoint,
+    fetch: fetchJSONFile,
+  });
 }
 
 export function getPreset({
   packageName: pkgName,
   presetName = 'default',
 }: PresetConfig): Promise<Preset> {
-  return getPresetFromEndpoint(pkgName, presetName);
+  return getPresetFromEndpoint(pkgName, presetName, Endpoint);
 }
diff --git a/lib/config/presets/util.spec.ts b/lib/config/presets/util.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..64ecca6364432ad23f84d9c9d2368be23704e380
--- /dev/null
+++ b/lib/config/presets/util.spec.ts
@@ -0,0 +1,67 @@
+import { getName } from '../../../test/util';
+import { Preset } from './common';
+import {
+  FetchPresetConfig,
+  PRESET_DEP_NOT_FOUND,
+  PRESET_NOT_FOUND,
+  fetchPreset,
+} from './util';
+
+const config: FetchPresetConfig = {
+  pkgName: 'some/repo',
+  filePreset: 'default',
+  endpoint: 'endpoint',
+  fetch: undefined,
+};
+
+const fetch = jest.fn(() => Promise.resolve<Preset>({}));
+
+describe(getName(__filename), () => {
+  beforeEach(() => {
+    fetch.mockReset();
+  });
+  it('works', async () => {
+    fetch.mockResolvedValue({ sub: { preset: { foo: true } } });
+    expect(await fetchPreset({ ...config, fetch })).toEqual({
+      sub: { preset: { foo: true } },
+    });
+
+    expect(
+      await fetchPreset({ ...config, filePreset: 'some/sub', fetch })
+    ).toEqual({ preset: { foo: true } });
+
+    expect(
+      await fetchPreset({ ...config, filePreset: 'some/sub/preset', fetch })
+    ).toEqual({ foo: true });
+  });
+
+  it('fails', async () => {
+    fetch.mockRejectedValueOnce(new Error('fails'));
+    await expect(fetchPreset({ ...config, fetch })).rejects.toThrow('fails');
+  });
+
+  it(PRESET_DEP_NOT_FOUND, async () => {
+    fetch.mockResolvedValueOnce(null);
+    await expect(fetchPreset({ ...config, fetch })).rejects.toThrow(
+      PRESET_DEP_NOT_FOUND
+    );
+
+    fetch.mockRejectedValueOnce(new Error(PRESET_DEP_NOT_FOUND));
+    fetch.mockRejectedValueOnce(new Error(PRESET_DEP_NOT_FOUND));
+    await expect(fetchPreset({ ...config, fetch })).rejects.toThrow(
+      PRESET_DEP_NOT_FOUND
+    );
+  });
+
+  it(PRESET_NOT_FOUND, async () => {
+    fetch.mockResolvedValueOnce({});
+    await expect(
+      fetchPreset({ ...config, filePreset: 'some/sub/preset', fetch })
+    ).rejects.toThrow(PRESET_NOT_FOUND);
+
+    fetch.mockResolvedValueOnce({ sub: {} });
+    await expect(
+      fetchPreset({ ...config, filePreset: 'some/sub/preset', fetch })
+    ).rejects.toThrow(PRESET_NOT_FOUND);
+  });
+});
diff --git a/lib/config/presets/util.ts b/lib/config/presets/util.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8c3b648c7616b1c6ae19540b76ba5af4e299e7f8
--- /dev/null
+++ b/lib/config/presets/util.ts
@@ -0,0 +1,63 @@
+import { logger } from '../../logger';
+import { ensureTrailingSlash } from '../../util/url';
+import { Preset } from './common';
+
+export const PRESET_DEP_NOT_FOUND = 'dep not found';
+export const PRESET_NOT_FOUND = 'preset not found';
+
+export type PresetFetcher = (
+  repo: string,
+  fileName: string,
+  endpoint: string
+) => Promise<Preset>;
+
+export type FetchPresetConfig = {
+  pkgName: string;
+  filePreset: string;
+  endpoint: string;
+  fetch: PresetFetcher;
+};
+
+export async function fetchPreset({
+  pkgName,
+  filePreset,
+  endpoint,
+  fetch,
+}: FetchPresetConfig): Promise<Preset | undefined> {
+  // eslint-disable-next-line no-param-reassign
+  endpoint = ensureTrailingSlash(endpoint);
+  const [fileName, presetName, subPresetName] = filePreset.split('/');
+  let jsonContent: any | undefined;
+  if (fileName === 'default') {
+    try {
+      jsonContent = await fetch(pkgName, 'default.json', endpoint);
+    } catch (err) {
+      if (err.message !== PRESET_DEP_NOT_FOUND) {
+        throw err;
+      }
+      logger.debug('default.json preset not found - trying renovate.json');
+      jsonContent = await fetch(pkgName, 'renovate.json', endpoint);
+    }
+  } else {
+    jsonContent = await fetch(pkgName, `${fileName}.json`, endpoint);
+  }
+
+  if (!jsonContent) {
+    throw new Error(PRESET_DEP_NOT_FOUND);
+  }
+  if (presetName) {
+    const preset = jsonContent[presetName];
+    if (!preset) {
+      throw new Error(PRESET_NOT_FOUND);
+    }
+    if (subPresetName) {
+      const subPreset = preset[subPresetName];
+      if (!subPreset) {
+        throw new Error(PRESET_NOT_FOUND);
+      }
+      return subPreset;
+    }
+    return preset;
+  }
+  return jsonContent;
+}