Модульное тестирование в Go с использованием Mockery

Вступление

В современной разработке программного обеспечения модульное тестирование необходимо для любого серьезного проекта.

Одна из проблем при написании модульных тестов на Go заключается в том, что наш код взаимодействует с файлами, веб-сайтами или любыми сторонними сервисами.

Mockery – это библиотека, которая помогает нам решать такие проблемы.

Структура файлов

В нашем примере мы хотим имитировать функцию, которая читает файл. Но в нашем тесте мы не хотим читать сам файл.

Во-первых, давайте взглянем на файловую структуру на высоком уровне:

fil е connector.go -> это структура, которая позволит нам читать из файла.

filesystem.go -> это высокоуровневая структура, которая будет использовать файловое соединение.

filesystem_test.go -> файл модульного теста для filesystem.go

Код

fileconnector.go.

package mockingdemo

import "os"

type FileConnector interface{
	ReadFromFile(name string) ([]byte, error)
}

type fileConnector struct{}

func NewFileConnector() FileConnector{
	return &fileConnector{}
}

func (f*fileConnector) ReadFromFile(filename string)  ([]byte, error){
	data, err := os.ReadFile(filename)
	if err != nil{
		return nil, err
	}
	return data, err
}

Все, что делает файловое соединение – это чтение из файла.

Структура fileConnector реализует интерфейс FileConnector.

Функция NewFileConnector (), возвращаемый тип – это интерфейс, но на самом деле она возвращает структуру fileConnector.

filesystem.go

package mockingdemo

import "fmt"

type FileSystem struct{
	conn FileConnector
}

func NewFS() FileSystem{
	fs := FileSystem{conn: NewFileConnector()}
	return fs
}

func (fs* FileSystem) PrintFileContent(fileName string) error{

	fileContent,err  := fs.conn.ReadFromFile(fileName)

	if err != nil {
		return  err
	}

	fmt.Println(string(fileContent))
	return nil
}

Установка и использование Mockery

Установка Mockery:

go get github.com/vektra/mockery/v2/…/

Теперь запустите Mockery в том же каталоге, что и файл filesystem.go.

mockery --all

Теперь у вас должен быть файл с именем FileConnector.go в папке mocks.

Содержимое этого файла:

// Code generated by mockery v2.9.4. DO NOT EDIT.

package mocks

import mock "github.com/stretchr/testify/mock"

// FileConnector is an autogenerated mock type for the FileConnector type
type FileConnector struct {
	mock.Mock
}

// ReadFromFile provides a mock function with given fields: name
func (_m *FileConnector) ReadFromFile(name string) ([]byte, error) {
	ret := _m.Called(name)

	var r0 []byte
	if rf, ok := ret.Get(0).(func(string) []byte); ok {
		r0 = rf(name)
	} else {
		if ret.Get(0) != nil {
			r0 = ret.Get(0).([]byte)
		}
	}

	var r1 error
	if rf, ok := ret.Get(1).(func(string) error); ok {
		r1 = rf(name)
	} else {
		r1 = ret.Error(1)
	}

	return r0, r1
}

Несколько примечаний:

  • Все типы интерфейсов, встречающиеся в нашем коде, теперь имитируются. Структура FileConnector реализует наш интерфейс.
  • У файла есть собственный пакет с именем mocks.
  • Никогда не редактируйте этот файл напрямую.
  • Если вы измените код, повторно сгенерируйте макеты с помощью команды, указанной выше.

Использование сгенерированного макета

Тестовый файл:

package mockingdemo

import (
	"golangtips/mockingdemo/mocks"
	"testing"
)

func TestFileSystem_PrintFileContent(t *testing.T) {
	type fields struct {
		conn FileConnector
	}
	
	const fileToRead = "mockFileToread.txt"

	connectorMock := mocks.FileConnector{}
	connectorMock.On("ReadFromFile", fileToRead).Return([]byte("hello"),nil)

	type args struct {
		fileName string
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		wantErr bool
	}{
		{
			"test",
			fields{&connectorMock},
			args{fileName: fileToRead},
			false,
		},

	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fs := &FileSystem{
				conn: tt.fields.conn,
			}
			if err := fs.PrintFileContent(tt.args.fileName); (err != nil) != tt.wantErr {
				t.Errorf("PrintFileContent() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

Важная строка:

connectorMock.On("ReadFromFile",fileToRead).Return([]byte("hello"),nil)

Это определяет поведение, которое мы хотим, чтобы макет имел. 

Вот и все. Теперь вы знаете, как использовать Mockery для вашего интерфейса, и как использовать его в модульных тестах!

Ответить