Victor Felipe
Victor Felipe

Reputation: 9

Invalid pointer references during unit test

I have a struct that represent a Vault. I'm trying test a scenario where a Player cannot retrieve a item that not exist in vault. The follow code represents this scenario.

vault.go

package vault

type Vault struct {
    valueobjects.VaultID
    Items      []*item.Item
    GoldAmount int
    sync.Mutex
}

func NewVault() *Vault {
    vault := Vault{
        VaultID:    valueobjects.NewVaultID(uuid.New()),
        Items:      []*item.Item{},
        GoldAmount: 0,
    }
    return &vault
}

func (v *Vault) AddItem(i *item.Item, p *player.Player) error {
    v.Lock()
    defer v.Unlock()

    err := p.RetriveItem(i)

    if err != nil {
        return NewVaultError(ErrInvalidOperation, err)
    }

    v.Items = append(v.Items, i)

    return nil
}

func (v *Vault) RetriveItem(i *item.Item, p *player.Player) error {
    v.Lock()
    defer v.Unlock()

    if len(v.Items) == 0 {
        return NewVaultError(ErrEmptyVault, nil)
    }

    for index, item := range v.Items {
        if item.ID() == i.ID() {
            if err := p.PickItem(i); err != nil {
                return NewVaultError(ErrInvalidOperation, err)
            }
            v.Items = append(v.Items[:index], v.Items[index+1:]...)
            return nil
        }
    }
    return NewVaultError(ErrItemNotFound, nil)
}

vault_test.go

package vault

var (
    vaultInstance *Vault
    vaultOnce     sync.Once
)

func TestInicializeVault(t *testing.T) {
    t.Cleanup(resetVault)
    vault := getVault()

    if vault == nil {
        t.Error("Error initializing vault")
        return
    }

    t.Run("Check initial vault gold amount", func(t *testing.T) {
        assert.Equal(t, 0, vault.GoldAmount)
    })

    t.Run("Check initial items stored", func(t *testing.T) {
        assert.Equal(t, 0, len(vault.Items))
    })

}

func TestVaultID(t *testing.T) {
    vault := NewVault()
    if vault == nil {
        t.Error("Error initializing vault")
        return
    }

    t.Run("Check vault ID", func(t *testing.T) {
        assert.NotEqual(t, "", vault.ID())
    })
}


func TestRetriveItem(t *testing.T) {
    t.Cleanup(resetVault)
    vault := NewVault()

    regPlayer, _ := player.NewPlayer("RegularPlayer", common.Ranger)
    maxInvPlayer, _ := player.NewPlayer("MaxInvPlayer", common.Warrior)

    item1 := item.PickRandomItem()
    item2 := item.PickRandomItem()

    fillPlayerInventory(regPlayer, item1, 2)
    fillPlayerInventory(maxInvPlayer, item2, 10)

    if vault == nil {
        t.Error("Error initializing vault")
        return
    }

    t.Run("Retrieve unexisting item from vault", func(t *testing.T) {
        var vaultError *VaultError

        vault.AddItem(item1, regPlayer)
        err := vault.RetriveItem(item.PickRandomItem(), regPlayer)

        assert.ErrorAs(t, err, &vaultError)

    })
}


func fillPlayerInventory(p *player.Player, i *item.Item, t int) {
    for j := 0; j <= t; j++ {
        p.PickItem(i)
    }
}

func resetVault() {
    vaultInstance = NewVault()
}

func getVault() *Vault {
    vaultOnce.Do(func() {
        vaultInstance = NewVault()
    })
    return vaultInstance
}

No matter what I do, the parameter that is being passed to RetriveItem is always item1.

I cant figure out what I'm doing wrong. I appreciate any help

I expect understand why my test doesn't work properly.

--- UPDATE with more definitions

item.go

package item

var (
    ITEMS = []string{"Sword", "Shield", "Helmet", "Boots", "Gloves", "Potion", "Ring", "Amulet", "Scroll", "Book"}
)

type Item struct {
    valueobjects.ItemID
    name string
}

func PickRandomItem() *Item {
    return initializeItem(ITEMS[rand.Intn(10)])
}

func (i *Item) Name() string {
    return i.name
}

func initializeItem(name string) *Item {
    return &Item{
        name: name,
    }
}

base_id.go

package common

type BaseID[T comparable] struct {
    value T
}

func NewBaseID[T comparable](value T) BaseID[T] {
    return BaseID[T]{value: value}
}

func (b BaseID[T]) ID() T {
    return b.value
}

func (this BaseID[T]) Equals(object any) bool {
    o, ok := object.(BaseID[T])
    return ok && this.value == o.value
}

vault_id.go and others Id definition are pretty much the same.

package valueobjects

type VaultID struct {
    common.BaseID[uuid.UUID]
}

func NewVaultID(value uuid.UUID) VaultID {
    return VaultID{
        BaseID: common.NewBaseID(value),
    }
}

func (pID VaultID) Equals(otherID VaultID) bool {
    return pID.BaseID.Equals(otherID.BaseID)
}

Upvotes: -4

Views: 54

Answers (1)

Victor Felipe
Victor Felipe

Reputation: 9

I think I found the issue. I still investigating, but I notice that my function to initializa items wasn't define and ID for Items.

After this moditification on that struct:

func initializeItem(name string) *Item {
    return &Item{
        ItemID: valueobjects.NewItemID(uuid.New()),
        name:   name,
    }
}

and change my tests a little bit:

    t.Run("Retrieve unexisting item from vault", func(t *testing.T) {
        var vaultErr *VaultError

        vault.AddItem(item1, regPlayer)
        vaultSize := len(vault.Items)

        err := vault.RetriveItem(item.PickRandomItem(), regPlayer)
        assert.Error(t, vault.RetriveItem(item3, regPlayer))

        assert.ErrorAs(t, err, &vaultErr)
        assert.Contains(t, vaultErr.Error(), ErrItemNotFound.Error())
        assert.Equal(t, vaultSize, len(vault.Items))

    })

It starting to working.

I need to do more investigation to understand this. But for now, it working!

Upvotes: 0

Related Questions