domodwyer / mailyak

An elegant MIME/SMTP email library with support for attachments

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

testability

jmhodges opened this issue · comments

Would love to use this project, but since buildMime is private, and the difficult to mock smtp.Auth interface is the only thing injected, it's hard to write tests of my email sending code.

I'm not sure what the best way to solve this problem would be from mailyak's side, but maybe you have an idea?

commented

Hi @jmhodges

You should interface the methods you use and inject an instance of mailyak into your code - mailyak internally has all the MIME generation covered with tests so you only need to test your interaction with it (in theory!)

Say you made your code accept an interface like:

type emailer interface {
	HTML() *mailyak.BodyPart
	Send() error
}

func myEmailFunc(email emailer) error {
	// Write the HTML body
	email.HTML().WriteString("content")

	// Do more things here - extend the interface as needed.

	return email.Send()
}

You could then pass myEmailFunc a mock implementation of emailer in your tests:

type mockEmailer struct {
	body    *mailyak.BodyPart
	sendErr error
}

func (m *mockEmailer) HTML() *mailyak.BodyPart {
	return m.body
}

func (m *mockEmailer) Send() error {
	return m.sendErr
}

And your tests would look like (simplified):

func TestMyEmailFunc(t *testing.T) {
	var tests = []struct {
		name  string
		email *mockEmailer
	}{
		{
			name: "no error",
			email: &mockEmailer{
				body: &mailyak.BodyPart{},
			},
		},
		{
			name: "with error",
			email: &mockEmailer{
				body:    &mailyak.BodyPart{},
				sendErr: errors.New("explosions"),
			},
		},
	}

	for _, tt := range tests {
		var tt = tt
		t.Run(tt.name, func(t *testing.T) {
			var err = myEmailFunc(tt.email)

			if tt.email.body.String() != "content" {
				t.Errorf("unexpected body: %q", tt.email.body.String())
			}

			if !reflect.DeepEqual(tt.email.sendErr, err) {
				t.Errorf("got err %v, want %v", err, tt.email.sendErr)
			}
		})
	}
}

Obviously these are simplified examples, but I hope they help! I'm going to close this ticket, but feel free to reply if this hasn't answered your question.

Dom

Except that mailyak has a lot of methods on it that I use, and it's not thread-safe, so I have to have a factory that produces an interface with a really complicated set of methods on it.

This seems less ideal and could be handled more cleanly in mailyak

commented

Hi @jmhodges

Interfacing your dependencies is a good habit for writing modular, SOLID code regardless of testing.

It's a pretty standard practise in the go world (and others):

I won't be exposing the private methods (thus increasing the number of "really complicated set of methods") to support an unusual testing method - interfacing really is the solution and it's easy!

Best of luck,
Dom