diff --git a/provider/consul/consul_catalog.go b/provider/consul/consul_catalog.go
index 5ea91f283..b6c1db4f4 100644
--- a/provider/consul/consul_catalog.go
+++ b/provider/consul/consul_catalog.go
@@ -2,7 +2,6 @@ package consul
 
 import (
 	"errors"
-	"strconv"
 	"strings"
 	"text/template"
 	"time"
@@ -95,7 +94,7 @@ func (p *CatalogProvider) Provide(configurationChan chan<- types.ConfigMessage,
 	}
 	p.client = client
 	p.Constraints = append(p.Constraints, constraints...)
-	p.setupFrontEndTemplate()
+	p.setupFrontEndRuleTemplate()
 
 	pool.Go(func(stop chan bool) {
 		notify := func(err error, time time.Duration) {
@@ -431,48 +430,7 @@ func (p *CatalogProvider) nodeFilter(service string, node *api.ServiceEntry) boo
 }
 
 func (p *CatalogProvider) isServiceEnabled(node *api.ServiceEntry) bool {
-	enable, err := strconv.ParseBool(p.getAttribute(label.SuffixEnable, node.Service.Tags, strconv.FormatBool(p.ExposedByDefault)))
-	if err != nil {
-		log.Debugf("Invalid value for enable, set to %b", p.ExposedByDefault)
-		return p.ExposedByDefault
-	}
-	return enable
-}
-
-func (p *CatalogProvider) getPrefixedName(name string) string {
-	if len(p.Prefix) > 0 && len(name) > 0 {
-		return p.Prefix + "." + name
-	}
-	return name
-}
-
-func (p *CatalogProvider) getAttribute(name string, tags []string, defaultValue string) string {
-	return getTag(p.getPrefixedName(name), tags, defaultValue)
-}
-
-func hasTag(name string, tags []string) bool {
-	// Very-very unlikely that a Consul tag would ever start with '=!='
-	tag := getTag(name, tags, "=!=")
-	return tag != "=!="
-}
-
-func getTag(name string, tags []string, defaultValue string) string {
-	for _, tag := range tags {
-		// Given the nature of Consul tags, which could be either singular markers, or key=value pairs, we check if the consul tag starts with 'name'
-		if strings.HasPrefix(strings.ToLower(tag), strings.ToLower(name)) {
-			// In case, where a tag might be a key=value, try to split it by the first '='
-			// - If the first element (which would always be there, even if the tag is a singular marker without '=' in it
-			if kv := strings.SplitN(tag, "=", 2); strings.ToLower(kv[0]) == strings.ToLower(name) {
-				// If the returned result is a key=value pair, return the 'value' component
-				if len(kv) == 2 {
-					return kv[1]
-				}
-				// If the returned result is a singular marker, return the 'key' component
-				return kv[0]
-			}
-		}
-	}
-	return defaultValue
+	return p.getBoolAttribute(label.SuffixEnable, node.Service.Tags, p.ExposedByDefault)
 }
 
 func (p *CatalogProvider) getConstraintTags(tags []string) []string {
diff --git a/provider/consul/consul_catalog_config.go b/provider/consul/consul_catalog_config.go
index 13c87f572..b66a86538 100644
--- a/provider/consul/consul_catalog_config.go
+++ b/provider/consul/consul_catalog_config.go
@@ -16,19 +16,23 @@ import (
 
 func (p *CatalogProvider) buildConfiguration(catalog []catalogUpdate) *types.Configuration {
 	var FuncMap = template.FuncMap{
+		"getAttribute": p.getAttribute,
+		"getTag":       getTag,
+		"hasTag":       hasTag,
+
+		// Backend functions
 		"getBackend":              getBackend,
-		"getFrontendRule":         p.getFrontendRule,
-		"getBackendName":          getBackendName,
 		"getBackendAddress":       getBackendAddress,
-		"getBasicAuth":            p.getBasicAuth,
+		"hasMaxconnAttributes":    p.hasMaxConnAttributes,
 		"getSticky":               p.getSticky,
 		"hasStickinessLabel":      p.hasStickinessLabel,
 		"getStickinessCookieName": p.getStickinessCookieName,
-		"getAttribute":            p.getAttribute,
-		"getTag":                  getTag,
-		"hasTag":                  hasTag,
-		"getEntryPoints":          getEntryPoints,
-		"hasMaxconnAttributes":    p.hasMaxConnAttributes,
+
+		// Frontend functions
+		"getBackendName":  getBackendName,
+		"getFrontendRule": p.getFrontendRule,
+		"getBasicAuth":    p.getBasicAuth,
+		"getEntryPoints":  getEntryPoints,
 	}
 
 	var allNodes []*api.ServiceEntry
@@ -58,7 +62,7 @@ func (p *CatalogProvider) buildConfiguration(catalog []catalogUpdate) *types.Con
 	return configuration
 }
 
-func (p *CatalogProvider) setupFrontEndTemplate() {
+func (p *CatalogProvider) setupFrontEndRuleTemplate() {
 	var FuncMap = template.FuncMap{
 		"getAttribute": p.getAttribute,
 		"getTag":       getTag,
@@ -68,6 +72,8 @@ func (p *CatalogProvider) setupFrontEndTemplate() {
 	p.frontEndRuleTemplate = tmpl
 }
 
+// Specific functions
+
 func (p *CatalogProvider) getFrontendRule(service serviceUpdate) string {
 	customFrontendRule := p.getAttribute(label.SuffixFrontendRule, service.Attributes, "")
 	if customFrontendRule == "" {
@@ -102,19 +108,16 @@ func (p *CatalogProvider) getFrontendRule(service serviceUpdate) string {
 }
 
 func (p *CatalogProvider) getBasicAuth(tags []string) []string {
-	list := p.getAttribute(label.SuffixFrontendAuthBasic, tags, "")
-	if list != "" {
-		return strings.Split(list, ",")
-	}
-	return []string{}
+	return p.getSliceAttribute(label.SuffixFrontendAuthBasic, tags)
 }
 
 func (p *CatalogProvider) hasMaxConnAttributes(attributes []string) bool {
 	amount := p.getAttribute(label.SuffixBackendMaxConnAmount, attributes, "")
-	extractorfunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, attributes, "")
-	return amount != "" && extractorfunc != ""
+	extractorFunc := p.getAttribute(label.SuffixBackendMaxConnExtractorFunc, attributes, "")
+	return amount != "" && extractorFunc != ""
 }
 
+// Deprecated
 func getEntryPoints(list string) []string {
 	return strings.Split(list, ",")
 }
@@ -146,7 +149,8 @@ func getBackendName(node *api.ServiceEntry, index int) string {
 }
 
 // TODO: Deprecated
-// Deprecated replaced by Stickiness
+// replaced by Stickiness
+// Deprecated
 func (p *CatalogProvider) getSticky(tags []string) string {
 	stickyTag := p.getAttribute(label.SuffixBackendLoadBalancerSticky, tags, "")
 	if len(stickyTag) > 0 {
@@ -165,3 +169,77 @@ func (p *CatalogProvider) hasStickinessLabel(tags []string) bool {
 func (p *CatalogProvider) getStickinessCookieName(tags []string) string {
 	return p.getAttribute(label.SuffixBackendLoadBalancerStickinessCookieName, tags, "")
 }
+
+// Base functions
+
+func (p *CatalogProvider) getSliceAttribute(name string, tags []string) []string {
+	rawValue := getTag(p.getPrefixedName(name), tags, "")
+
+	if len(rawValue) == 0 {
+		return nil
+	}
+	return label.SplitAndTrimString(rawValue, ",")
+}
+
+func (p *CatalogProvider) getBoolAttribute(name string, tags []string, defaultValue bool) bool {
+	rawValue := getTag(p.getPrefixedName(name), tags, "")
+
+	if len(rawValue) == 0 {
+		return defaultValue
+	}
+
+	value, err := strconv.ParseBool(rawValue)
+	if err != nil {
+		log.Errorf("Invalid value for %s: %s", name, rawValue)
+		return defaultValue
+	}
+	return value
+}
+
+func (p *CatalogProvider) getAttribute(name string, tags []string, defaultValue string) string {
+	return getTag(p.getPrefixedName(name), tags, defaultValue)
+}
+
+func (p *CatalogProvider) getPrefixedName(name string) string {
+	if len(p.Prefix) > 0 && len(name) > 0 {
+		return p.Prefix + "." + name
+	}
+	return name
+}
+
+func hasTag(name string, tags []string) bool {
+	lowerName := strings.ToLower(name)
+
+	for _, tag := range tags {
+		lowerTag := strings.ToLower(tag)
+
+		// Given the nature of Consul tags, which could be either singular markers, or key=value pairs
+		if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
+			return true
+		}
+	}
+	return false
+}
+
+func getTag(name string, tags []string, defaultValue string) string {
+	lowerName := strings.ToLower(name)
+
+	for _, tag := range tags {
+		lowerTag := strings.ToLower(tag)
+
+		// Given the nature of Consul tags, which could be either singular markers, or key=value pairs
+		if strings.HasPrefix(lowerTag, lowerName+"=") || lowerTag == lowerName {
+			// In case, where a tag might be a key=value, try to split it by the first '='
+			kv := strings.SplitN(tag, "=", 2)
+
+			// If the returned result is a key=value pair, return the 'value' component
+			if len(kv) == 2 {
+				return kv[1]
+			}
+			// If the returned result is a singular marker, return the 'key' component
+			return kv[0]
+		}
+
+	}
+	return defaultValue
+}
diff --git a/provider/consul/consul_catalog_config_test.go b/provider/consul/consul_catalog_config_test.go
new file mode 100644
index 000000000..edf0f60fb
--- /dev/null
+++ b/provider/consul/consul_catalog_config_test.go
@@ -0,0 +1,571 @@
+package consul
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/containous/traefik/provider/label"
+	"github.com/containous/traefik/types"
+	"github.com/hashicorp/consul/api"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBuildConfiguration(t *testing.T) {
+	provider := &CatalogProvider{
+		Domain:               "localhost",
+		Prefix:               "traefik",
+		ExposedByDefault:     false,
+		FrontEndRule:         "Host:{{.ServiceName}}.{{.Domain}}",
+		frontEndRuleTemplate: template.New("consul catalog frontend rule"),
+	}
+
+	testCases := []struct {
+		desc              string
+		nodes             []catalogUpdate
+		expectedFrontends map[string]*types.Frontend
+		expectedBackends  map[string]*types.Backend
+	}{
+		{
+			desc:              "Should build config of nothing",
+			nodes:             []catalogUpdate{},
+			expectedFrontends: map[string]*types.Frontend{},
+			expectedBackends:  map[string]*types.Backend{},
+		},
+		{
+			desc: "Should build config with no frontend and backend",
+			nodes: []catalogUpdate{
+				{
+					Service: &serviceUpdate{
+						ServiceName: "test",
+					},
+				},
+			},
+			expectedFrontends: map[string]*types.Frontend{},
+			expectedBackends:  map[string]*types.Backend{},
+		},
+		{
+			desc: "Should build config who contains one frontend and one backend",
+			nodes: []catalogUpdate{
+				{
+					Service: &serviceUpdate{
+						ServiceName: "test",
+						Attributes: []string{
+							"traefik.backend.loadbalancer=drr",
+							"traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5",
+							"random.foo=bar",
+							"traefik.backend.maxconn.amount=1000",
+							"traefik.backend.maxconn.extractorfunc=client.ip",
+							"traefik.frontend.auth.basic=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
+						},
+					},
+					Nodes: []*api.ServiceEntry{
+						{
+							Service: &api.AgentService{
+								Service: "test",
+								Address: "127.0.0.1",
+								Port:    80,
+								Tags: []string{
+									"traefik.backend.weight=42",
+									"random.foo=bar",
+									"traefik.backend.passHostHeader=true",
+									"traefik.protocol=https",
+								},
+							},
+							Node: &api.Node{
+								Node:    "localhost",
+								Address: "127.0.0.1",
+							},
+						},
+					},
+				},
+			},
+			expectedFrontends: map[string]*types.Frontend{
+				"frontend-test": {
+					Backend:        "backend-test",
+					PassHostHeader: true,
+					Routes: map[string]types.Route{
+						"route-host-test": {
+							Rule: "Host:test.localhost",
+						},
+					},
+					BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
+				},
+			},
+			expectedBackends: map[string]*types.Backend{
+				"backend-test": {
+					Servers: map[string]types.Server{
+						"test--127-0-0-1--80--traefik-backend-weight-42--random-foo-bar--traefik-backend-passHostHeader-true--traefik-protocol-https--0": {
+							URL:    "https://127.0.0.1:80",
+							Weight: 42,
+						},
+					},
+					CircuitBreaker: &types.CircuitBreaker{
+						Expression: "NetworkErrorRatio() > 0.5",
+					},
+					LoadBalancer: &types.LoadBalancer{
+						Method: "drr",
+					},
+					MaxConn: &types.MaxConn{
+						Amount:        1000,
+						ExtractorFunc: "client.ip",
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			actualConfig := provider.buildConfiguration(test.nodes)
+			assert.NotNil(t, actualConfig)
+			assert.Equal(t, test.expectedBackends, actualConfig.Backends)
+			assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
+		})
+	}
+}
+
+func TestGetTag(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		tags         []string
+		key          string
+		defaultValue string
+		expected     string
+	}{
+		{
+			desc: "Should return value of foo.bar key",
+			tags: []string{
+				"foo.bar=random",
+				"traefik.backend.weight=42",
+				"management",
+			},
+			key:          "foo.bar",
+			defaultValue: "0",
+			expected:     "random",
+		},
+		{
+			desc: "Should return default value when nonexistent key",
+			tags: []string{
+				"foo.bar.foo.bar=random",
+				"traefik.backend.weight=42",
+				"management",
+			},
+			key:          "foo.bar",
+			defaultValue: "0",
+			expected:     "0",
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			actual := getTag(test.key, test.tags, test.defaultValue)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func TestHasTag(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		name     string
+		tags     []string
+		expected bool
+	}{
+		{
+			desc:     "tag without value",
+			name:     "foo",
+			tags:     []string{"foo"},
+			expected: true,
+		},
+		{
+			desc:     "tag with value",
+			name:     "foo",
+			tags:     []string{"foo=true"},
+			expected: true,
+		},
+		{
+			desc:     "missing tag",
+			name:     "foo",
+			tags:     []string{"foobar=true"},
+			expected: false,
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			actual := hasTag(test.name, test.tags)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func TestGetPrefixedName(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		name     string
+		prefix   string
+		expected string
+	}{
+		{
+			desc:     "empty name with prefix",
+			name:     "",
+			prefix:   "foo",
+			expected: "",
+		},
+		{
+			desc:     "empty name without prefix",
+			name:     "",
+			prefix:   "",
+			expected: "",
+		},
+		{
+			desc:     "with prefix",
+			name:     "bar",
+			prefix:   "foo",
+			expected: "foo.bar",
+		},
+		{
+			desc:     "without prefix",
+			name:     "bar",
+			prefix:   "",
+			expected: "bar",
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			pro := &CatalogProvider{Prefix: test.prefix}
+
+			actual := pro.getPrefixedName(test.name)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+
+}
+
+func TestGetAttribute(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		tags         []string
+		key          string
+		defaultValue string
+		prefix       string
+		expected     string
+	}{
+		{
+			desc:   "Should return tag value 42",
+			prefix: "traefik",
+			tags: []string{
+				"foo.bar=ramdom",
+				"traefik.backend.weight=42",
+			},
+			key:          "backend.weight",
+			defaultValue: "0",
+			expected:     "42",
+		},
+		{
+			desc:   "Should return tag default value 0",
+			prefix: "traefik",
+			tags: []string{
+				"foo.bar=ramdom",
+				"traefik.backend.wei=42",
+			},
+			key:          "backend.weight",
+			defaultValue: "0",
+			expected:     "0",
+		},
+		{
+			desc: "Should return tag value 42 when empty prefix",
+			tags: []string{
+				"foo.bar=ramdom",
+				"backend.weight=42",
+			},
+			key:          "backend.weight",
+			defaultValue: "0",
+			expected:     "42",
+		},
+		{
+			desc: "Should return default value 0 when empty prefix",
+			tags: []string{
+				"foo.bar=ramdom",
+				"backend.wei=42",
+			},
+			key:          "backend.weight",
+			defaultValue: "0",
+			expected:     "0",
+		},
+		{
+			desc: "Should return for.bar key value random when empty prefix",
+			tags: []string{
+				"foo.bar=ramdom",
+				"backend.wei=42",
+			},
+			key:          "foo.bar",
+			defaultValue: "random",
+			expected:     "ramdom",
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			p := &CatalogProvider{
+				Domain: "localhost",
+				Prefix: test.prefix,
+			}
+
+			actual := p.getAttribute(test.key, test.tags, test.defaultValue)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func TestGetFrontendRule(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		service  serviceUpdate
+		expected string
+	}{
+		{
+			desc: "Should return default host foo.localhost",
+			service: serviceUpdate{
+				ServiceName: "foo",
+				Attributes:  []string{},
+			},
+			expected: "Host:foo.localhost",
+		},
+		{
+			desc: "Should return host *.example.com",
+			service: serviceUpdate{
+				ServiceName: "foo",
+				Attributes: []string{
+					"traefik.frontend.rule=Host:*.example.com",
+				},
+			},
+			expected: "Host:*.example.com",
+		},
+		{
+			desc: "Should return host foo.example.com",
+			service: serviceUpdate{
+				ServiceName: "foo",
+				Attributes: []string{
+					"traefik.frontend.rule=Host:{{.ServiceName}}.example.com",
+				},
+			},
+			expected: "Host:foo.example.com",
+		},
+		{
+			desc: "Should return path prefix /bar",
+			service: serviceUpdate{
+				ServiceName: "foo",
+				Attributes: []string{
+					"traefik.frontend.rule=PathPrefix:{{getTag \"contextPath\" .Attributes \"/\"}}",
+					"contextPath=/bar",
+				},
+			},
+			expected: "PathPrefix:/bar",
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			provider := &CatalogProvider{
+				Domain:               "localhost",
+				Prefix:               "traefik",
+				FrontEndRule:         "Host:{{.ServiceName}}.{{.Domain}}",
+				frontEndRuleTemplate: template.New("consul catalog frontend rule"),
+			}
+			provider.setupFrontEndRuleTemplate()
+
+			actual := provider.getFrontendRule(test.service)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func TestGetBackendAddress(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		node     *api.ServiceEntry
+		expected string
+	}{
+		{
+			desc: "Should return the address of the service",
+			node: &api.ServiceEntry{
+				Node: &api.Node{
+					Address: "10.1.0.1",
+				},
+				Service: &api.AgentService{
+					Address: "10.2.0.1",
+				},
+			},
+			expected: "10.2.0.1",
+		},
+		{
+			desc: "Should return the address of the node",
+			node: &api.ServiceEntry{
+				Node: &api.Node{
+					Address: "10.1.0.1",
+				},
+				Service: &api.AgentService{
+					Address: "",
+				},
+			},
+			expected: "10.1.0.1",
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			actual := getBackendAddress(test.node)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func TestGetBackendName(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		node     *api.ServiceEntry
+		expected string
+	}{
+		{
+			desc: "Should create backend name without tags",
+			node: &api.ServiceEntry{
+				Service: &api.AgentService{
+					Service: "api",
+					Address: "10.0.0.1",
+					Port:    80,
+					Tags:    []string{},
+				},
+			},
+			expected: "api--10-0-0-1--80--0",
+		},
+		{
+			desc: "Should create backend name with multiple tags",
+			node: &api.ServiceEntry{
+				Service: &api.AgentService{
+					Service: "api",
+					Address: "10.0.0.1",
+					Port:    80,
+					Tags:    []string{"traefik.weight=42", "traefik.enable=true"},
+				},
+			},
+			expected: "api--10-0-0-1--80--traefik-weight-42--traefik-enable-true--1",
+		},
+		{
+			desc: "Should create backend name with one tag",
+			node: &api.ServiceEntry{
+				Service: &api.AgentService{
+					Service: "api",
+					Address: "10.0.0.1",
+					Port:    80,
+					Tags:    []string{"a funny looking tag"},
+				},
+			},
+			expected: "api--10-0-0-1--80--a-funny-looking-tag--2",
+		},
+	}
+
+	for i, test := range testCases {
+		test := test
+		i := i
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			actual := getBackendName(test.node, i)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func TestGetBasicAuth(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		tags     []string
+		expected []string
+	}{
+		{
+			desc:     "label missing",
+			tags:     []string{},
+			expected: []string{},
+		},
+		{
+			desc: "label existing",
+			tags: []string{
+				"traefik.frontend.auth.basic=user:password",
+			},
+			expected: []string{"user:password"},
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+			provider := &CatalogProvider{
+				Prefix: "traefik",
+			}
+			actual := provider.getBasicAuth(test.tags)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func TestHasStickinessLabel(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		tags     []string
+		expected bool
+	}{
+		{
+			desc:     "label missing",
+			tags:     []string{},
+			expected: false,
+		},
+		{
+			desc: "stickiness=true",
+			tags: []string{
+				label.TraefikBackendLoadBalancerStickiness + "=true",
+			},
+			expected: true,
+		},
+		{
+			desc: "stickiness=false",
+			tags: []string{
+				label.TraefikBackendLoadBalancerStickiness + "=false",
+			},
+			expected: false,
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			actual := hasStickinessLabel(test.tags)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
diff --git a/provider/consul/consul_catalog_test.go b/provider/consul/consul_catalog_test.go
index 26517a348..208850f6c 100644
--- a/provider/consul/consul_catalog_test.go
+++ b/provider/consul/consul_catalog_test.go
@@ -3,527 +3,12 @@ package consul
 import (
 	"sort"
 	"testing"
-	"text/template"
 
 	"github.com/BurntSushi/ty/fun"
-	"github.com/containous/traefik/provider/label"
-	"github.com/containous/traefik/types"
 	"github.com/hashicorp/consul/api"
 	"github.com/stretchr/testify/assert"
 )
 
-func TestGetPrefixedName(t *testing.T) {
-	testCases := []struct {
-		desc     string
-		name     string
-		prefix   string
-		expected string
-	}{
-		{
-			desc:     "empty name with prefix",
-			name:     "",
-			prefix:   "foo",
-			expected: "",
-		},
-		{
-			desc:     "empty name without prefix",
-			name:     "",
-			prefix:   "",
-			expected: "",
-		},
-		{
-			desc:     "with prefix",
-			name:     "bar",
-			prefix:   "foo",
-			expected: "foo.bar",
-		},
-		{
-			desc:     "without prefix",
-			name:     "bar",
-			prefix:   "",
-			expected: "bar",
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			pro := &CatalogProvider{Prefix: test.prefix}
-
-			actual := pro.getPrefixedName(test.name)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-
-}
-
-func TestGetFrontendRule(t *testing.T) {
-
-	testCases := []struct {
-		desc     string
-		service  serviceUpdate
-		expected string
-	}{
-		{
-			desc: "Should return default host foo.localhost",
-			service: serviceUpdate{
-				ServiceName: "foo",
-				Attributes:  []string{},
-			},
-			expected: "Host:foo.localhost",
-		},
-		{
-			desc: "Should return host *.example.com",
-			service: serviceUpdate{
-				ServiceName: "foo",
-				Attributes: []string{
-					"traefik.frontend.rule=Host:*.example.com",
-				},
-			},
-			expected: "Host:*.example.com",
-		},
-		{
-			desc: "Should return host foo.example.com",
-			service: serviceUpdate{
-				ServiceName: "foo",
-				Attributes: []string{
-					"traefik.frontend.rule=Host:{{.ServiceName}}.example.com",
-				},
-			},
-			expected: "Host:foo.example.com",
-		},
-		{
-			desc: "Should return path prefix /bar",
-			service: serviceUpdate{
-				ServiceName: "foo",
-				Attributes: []string{
-					"traefik.frontend.rule=PathPrefix:{{getTag \"contextPath\" .Attributes \"/\"}}",
-					"contextPath=/bar",
-				},
-			},
-			expected: "PathPrefix:/bar",
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			provider := &CatalogProvider{
-				Domain:               "localhost",
-				Prefix:               "traefik",
-				FrontEndRule:         "Host:{{.ServiceName}}.{{.Domain}}",
-				frontEndRuleTemplate: template.New("consul catalog frontend rule"),
-			}
-			provider.setupFrontEndTemplate()
-
-			actual := provider.getFrontendRule(test.service)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-}
-
-func TestGetTag(t *testing.T) {
-	testCases := []struct {
-		desc         string
-		tags         []string
-		key          string
-		defaultValue string
-		expected     string
-	}{
-		{
-			desc: "Should return value of foo.bar key",
-			tags: []string{
-				"foo.bar=random",
-				"traefik.backend.weight=42",
-				"management",
-			},
-			key:          "foo.bar",
-			defaultValue: "0",
-			expected:     "random",
-		},
-		{
-			desc: "Should return default value when nonexistent key",
-			tags: []string{
-				"foo.bar.foo.bar=random",
-				"traefik.backend.weight=42",
-				"management",
-			},
-			key:          "foo.bar",
-			defaultValue: "0",
-			expected:     "0",
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			actual := getTag(test.key, test.tags, test.defaultValue)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-}
-
-func TestHasTag(t *testing.T) {
-	testCases := []struct {
-		desc     string
-		name     string
-		tags     []string
-		expected bool
-	}{
-		{
-			desc:     "tag without value",
-			name:     "foo",
-			tags:     []string{"foo"},
-			expected: true,
-		},
-		{
-			desc:     "tag with value",
-			name:     "foo",
-			tags:     []string{"foo=true"},
-			expected: true,
-		},
-		{
-			desc:     "missing tag",
-			name:     "foo",
-			tags:     []string{"foobar=true"},
-			expected: false,
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			actual := hasTag(test.name, test.tags)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-}
-
-func TestGetAttribute(t *testing.T) {
-	provider := &CatalogProvider{
-		Domain: "localhost",
-		Prefix: "traefik",
-	}
-
-	testCases := []struct {
-		desc         string
-		tags         []string
-		key          string
-		defaultValue string
-		expected     string
-	}{
-		{
-			desc: "Should return tag value 42",
-			tags: []string{
-				"foo.bar=ramdom",
-				"traefik.backend.weight=42",
-			},
-			key:          "backend.weight",
-			defaultValue: "0",
-			expected:     "42",
-		},
-		{
-			desc: "Should return tag default value 0",
-			tags: []string{
-				"foo.bar=ramdom",
-				"traefik.backend.wei=42",
-			},
-			key:          "backend.weight",
-			defaultValue: "0",
-			expected:     "0",
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			actual := provider.getAttribute(test.key, test.tags, test.defaultValue)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-}
-
-func TestGetAttributeWithEmptyPrefix(t *testing.T) {
-	provider := &CatalogProvider{
-		Domain: "localhost",
-		Prefix: "",
-	}
-
-	testCases := []struct {
-		desc         string
-		tags         []string
-		key          string
-		defaultValue string
-		expected     string
-	}{
-		{
-			desc: "Should return tag value 42",
-			tags: []string{
-				"foo.bar=ramdom",
-				"backend.weight=42",
-			},
-			key:          "backend.weight",
-			defaultValue: "0",
-			expected:     "42",
-		},
-		{
-			desc: "Should return default value 0",
-			tags: []string{
-				"foo.bar=ramdom",
-				"backend.wei=42",
-			},
-			key:          "backend.weight",
-			defaultValue: "0",
-			expected:     "0",
-		},
-		{
-			desc: "Should return for.bar key value random",
-			tags: []string{
-				"foo.bar=ramdom",
-				"backend.wei=42",
-			},
-			key:          "foo.bar",
-			defaultValue: "random",
-			expected:     "ramdom",
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			actual := provider.getAttribute(test.key, test.tags, test.defaultValue)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-}
-
-func TestGetBackendAddress(t *testing.T) {
-	testCases := []struct {
-		desc     string
-		node     *api.ServiceEntry
-		expected string
-	}{
-		{
-			desc: "Should return the address of the service",
-			node: &api.ServiceEntry{
-				Node: &api.Node{
-					Address: "10.1.0.1",
-				},
-				Service: &api.AgentService{
-					Address: "10.2.0.1",
-				},
-			},
-			expected: "10.2.0.1",
-		},
-		{
-			desc: "Should return the address of the node",
-			node: &api.ServiceEntry{
-				Node: &api.Node{
-					Address: "10.1.0.1",
-				},
-				Service: &api.AgentService{
-					Address: "",
-				},
-			},
-			expected: "10.1.0.1",
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			actual := getBackendAddress(test.node)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-}
-
-func TestGetBackendName(t *testing.T) {
-	testCases := []struct {
-		desc     string
-		node     *api.ServiceEntry
-		expected string
-	}{
-		{
-			desc: "Should create backend name without tags",
-			node: &api.ServiceEntry{
-				Service: &api.AgentService{
-					Service: "api",
-					Address: "10.0.0.1",
-					Port:    80,
-					Tags:    []string{},
-				},
-			},
-			expected: "api--10-0-0-1--80--0",
-		},
-		{
-			desc: "Should create backend name with multiple tags",
-			node: &api.ServiceEntry{
-				Service: &api.AgentService{
-					Service: "api",
-					Address: "10.0.0.1",
-					Port:    80,
-					Tags:    []string{"traefik.weight=42", "traefik.enable=true"},
-				},
-			},
-			expected: "api--10-0-0-1--80--traefik-weight-42--traefik-enable-true--1",
-		},
-		{
-			desc: "Should create backend name with one tag",
-			node: &api.ServiceEntry{
-				Service: &api.AgentService{
-					Service: "api",
-					Address: "10.0.0.1",
-					Port:    80,
-					Tags:    []string{"a funny looking tag"},
-				},
-			},
-			expected: "api--10-0-0-1--80--a-funny-looking-tag--2",
-		},
-	}
-
-	for i, test := range testCases {
-		test := test
-		i := i
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			actual := getBackendName(test.node, i)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-}
-
-func TestBuildConfiguration(t *testing.T) {
-	provider := &CatalogProvider{
-		Domain:               "localhost",
-		Prefix:               "traefik",
-		ExposedByDefault:     false,
-		FrontEndRule:         "Host:{{.ServiceName}}.{{.Domain}}",
-		frontEndRuleTemplate: template.New("consul catalog frontend rule"),
-	}
-
-	testCases := []struct {
-		desc              string
-		nodes             []catalogUpdate
-		expectedFrontends map[string]*types.Frontend
-		expectedBackends  map[string]*types.Backend
-	}{
-		{
-			desc:              "Should build config of nothing",
-			nodes:             []catalogUpdate{},
-			expectedFrontends: map[string]*types.Frontend{},
-			expectedBackends:  map[string]*types.Backend{},
-		},
-		{
-			desc: "Should build config with no frontend and backend",
-			nodes: []catalogUpdate{
-				{
-					Service: &serviceUpdate{
-						ServiceName: "test",
-					},
-				},
-			},
-			expectedFrontends: map[string]*types.Frontend{},
-			expectedBackends:  map[string]*types.Backend{},
-		},
-		{
-			desc: "Should build config who contains one frontend and one backend",
-			nodes: []catalogUpdate{
-				{
-					Service: &serviceUpdate{
-						ServiceName: "test",
-						Attributes: []string{
-							"traefik.backend.loadbalancer=drr",
-							"traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5",
-							"random.foo=bar",
-							"traefik.backend.maxconn.amount=1000",
-							"traefik.backend.maxconn.extractorfunc=client.ip",
-							"traefik.frontend.auth.basic=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
-						},
-					},
-					Nodes: []*api.ServiceEntry{
-						{
-							Service: &api.AgentService{
-								Service: "test",
-								Address: "127.0.0.1",
-								Port:    80,
-								Tags: []string{
-									"traefik.backend.weight=42",
-									"random.foo=bar",
-									"traefik.backend.passHostHeader=true",
-									"traefik.protocol=https",
-								},
-							},
-							Node: &api.Node{
-								Node:    "localhost",
-								Address: "127.0.0.1",
-							},
-						},
-					},
-				},
-			},
-			expectedFrontends: map[string]*types.Frontend{
-				"frontend-test": {
-					Backend:        "backend-test",
-					PassHostHeader: true,
-					Routes: map[string]types.Route{
-						"route-host-test": {
-							Rule: "Host:test.localhost",
-						},
-					},
-					BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
-				},
-			},
-			expectedBackends: map[string]*types.Backend{
-				"backend-test": {
-					Servers: map[string]types.Server{
-						"test--127-0-0-1--80--traefik-backend-weight-42--random-foo-bar--traefik-backend-passHostHeader-true--traefik-protocol-https--0": {
-							URL:    "https://127.0.0.1:80",
-							Weight: 42,
-						},
-					},
-					CircuitBreaker: &types.CircuitBreaker{
-						Expression: "NetworkErrorRatio() > 0.5",
-					},
-					LoadBalancer: &types.LoadBalancer{
-						Method: "drr",
-					},
-					MaxConn: &types.MaxConn{
-						Amount:        1000,
-						ExtractorFunc: "client.ip",
-					},
-				},
-			},
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			actualConfig := provider.buildConfiguration(test.nodes)
-			assert.Equal(t, test.expectedBackends, actualConfig.Backends)
-			assert.Equal(t, test.expectedFrontends, actualConfig.Frontends)
-		})
-	}
-}
-
 func TestNodeSorter(t *testing.T) {
 	testCases := []struct {
 		desc     string
@@ -972,81 +457,6 @@ func TestFilterEnabled(t *testing.T) {
 	}
 }
 
-func TestGetBasicAuth(t *testing.T) {
-	testCases := []struct {
-		desc     string
-		tags     []string
-		expected []string
-	}{
-		{
-			desc:     "label missing",
-			tags:     []string{},
-			expected: []string{},
-		},
-		{
-			desc: "label existing",
-			tags: []string{
-				"traefik.frontend.auth.basic=user:password",
-			},
-			expected: []string{"user:password"},
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-			provider := &CatalogProvider{
-				Prefix: "traefik",
-			}
-			actual := provider.getBasicAuth(test.tags)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-}
-
-func TestHasStickinessLabel(t *testing.T) {
-	p := &CatalogProvider{
-		Prefix: "traefik",
-	}
-
-	testCases := []struct {
-		desc     string
-		tags     []string
-		expected bool
-	}{
-		{
-			desc:     "label missing",
-			tags:     []string{},
-			expected: false,
-		},
-		{
-			desc: "stickiness=true",
-			tags: []string{
-				label.TraefikBackendLoadBalancerStickiness + "=true",
-			},
-			expected: true,
-		},
-		{
-			desc: "stickiness=false",
-			tags: []string{
-				label.TraefikBackendLoadBalancerStickiness + "=false",
-			},
-			expected: false,
-		},
-	}
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			actual := p.hasStickinessLabel(test.tags)
-			assert.Equal(t, test.expected, actual)
-		})
-	}
-}
-
 func TestGetChangedStringKeys(t *testing.T) {
 	testCases := []struct {
 		desc            string
@@ -1096,7 +506,7 @@ func TestGetChangedStringKeys(t *testing.T) {
 	}
 }
 
-func TestHasNodeOrTagschanged(t *testing.T) {
+func TestHasServiceChanged(t *testing.T) {
 	testCases := []struct {
 		desc     string
 		current  map[string]Service