AungHtetPaing__'s avatar

The given data was invalid in Phpunit

In my blog project, creating post is working. But i has problem in testing.

Here is my createblog.vue

<template>
  <div>
    <form @submit.prevent="Publish">
      <div class="form-group">
        <input
          type="file"
          class="form-control border-0 p-0"
          @change="imageSelected"
        />
        <div v-if="previewImage">
          <img :src="previewImage" style="height: 200px" class="rounded" />
        </div>
        <span class="text-danger" v-text="errors.get('featured_image')"></span>
      </div>
      <div class="form-group">
        <input
          type="text"
          placeholder="Title"
          class="form-control"
          v-model="title"
        />
        <span class="text-danger" v-text="errors.get('title')"></span>
      </div>
      <div class="form-group">
        <input
          type="text"
          placeholder="Write a excerpt for post ..."
          class="form-control"
          v-model="excerpt"
        />
        <span class="text-danger" v-text="errors.get('excerpt')"></span>
      </div>
      <div class="form-group">
        <vue-editor v-model="content"/>
        <span class="text-danger" v-text="errors.get('content')"></span>
      </div>
      <div class="form-group">
        <multiselect
          v-model="selectedTags" :options="tagOptions"
          :multiple="true" track-by="id"
          label="name" placeholder="Search or add tags"
        >
        </multiselect>
        <span class="text-danger" v-text="errors.get('tags')"></span>
      </div>
      <div v-if="isSchedule" class="form-group text-dark mb-3">
        <label for="published_at">Schedule your publish time (* if you blank this field, post will publish with current time)</label>
        <!-- <input
          type="text" step="1" class="form-control"
          v-model="published_at" placeholder="dd/mm/yyyy, H:i:s"
          onfocus="(this.type='datetime-local')"
        /> -->
        <input
          type="datetime-local" step="1" class="form-control"
          v-model="published_at"
        />
        <span class="text-danger" v-text="errors.get('published_at')"></span>
      </div>
      <div class="mt-3">
        <button class="btn btn-sm btn-success">
          Publish
        </button>
        <!-- for next improvement :)
          <button name="draft" type="submit" class="btn btn-sm btn-outline-success">
          Save as draft
        </button> -->
        -Or-
        <a class="btn btn-sm btn-link pl-0" v-if="isSchedule" @click="isSchedule = false">
          Cancel scheduling
        </a>
        <a v-else class="btn btn-sm btn-link pl-0" @click="isSchedule = true">
          Schedule for later
        </a>
      </div>
    </form>
  </div>
</template>

<script>
// import Quill from 'quill'
import Errors from "../form";
import Multiselect from "vue-multiselect";
import { VueEditor } from "vue2-editor";
import axios from "axios";

export default {
  components: {
    VueEditor,
    Multiselect,
  },
  // props: ["current"],
  data() {
    return {
      title: "",
      excerpt: "",
      content: "",
      featured_image: "",
      published_at: "",
      previewImage: "",
      selectedTags: [],
      tagOptions: [],
      errors: new Errors(),
      isSchedule: false,
    };
  },
  methods: {
    imageSelected(e) {
      this.featured_image = e.target.files[0];
      let reader = new FileReader();
      reader.readAsDataURL(this.featured_image);
      reader.onload = (e) => {
        this.previewImage = e.target.result;
      };
    },
    Publish() {
      let fd = new FormData();
      fd.append("featured_image", this.featured_image);
      fd.append("title", this.title);
      fd.append("excerpt", this.excerpt);
      fd.append("content", this.content);
      fd.append("published_at", this.published_at);
      fd.append("tags", JSON.stringify(this.selectedTags));

      axios
        .post(`/posts`, fd)
        .then((response) => {
          window.location.href = `https://hielo.test/posts/${response.data.postId}`;
          // console.log(JSON.stringify(this.selectedTags))
        })
        .catch((err) => this.errors.record(err.response.data.errors));
    },
    fetchTag() {
      axios
        .get(`/api/tags`)
        .then((res) => (this.tagOptions = res.data.data))
        .catch((err) => this.errors.record(err.response.data));
    },
  },
  created() {
    this.fetchTag();
  },
};
</script>

<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>

<style lang="scss" scoped>
</style>

postscontroller@store

public function store(StorePostRequest $request)
    {
        $attributes = $request->except('tags');

        if ($request->hasFile('featured_image')) {
            $attributes['featured_image'] = request('featured_image')->store('featured-images');
        }

        if (request('published_at') != null) {
            $attributes['published_at'] = request('published_at');

            $post = current_user()->posts()->create($attributes);
        } else {
            $attributes['published_at'] = date('Y-m-d\TH:i:s');

            $post = current_user()->posts()->create($attributes);
        }

        $tags = json_decode(request('tags'));

        foreach ($tags as $tag) {
            $tagsId[$tag->id] = ['tag_id' => $tag->id];
        }

        $post->tags()->sync($tagsId);

        foreach (current_user()->followers as $follower) {
            $follower->notify(new PostPublished($post));
        }

        \Session::flash('status', 'Post created success');

        return response()->json(['postId' => $post->id]);
    }

StorePostRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use App\Post;
use Illuminate\Support\Facades\Gate;
use App\Exceptions\ThrottleException;

class StorePostRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return Gate::allows('create_post', new Post);
    }

    protected function failedAuthorization()
    {
        throw new ThrottleException(
            'Action too frequently. Not allowed'
        );
    }

    /**
     * Prepare the data for validation.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
        if (is_string(request('tags'))) {
            $this->merge([
                'tags' => json_decode(request('tags')),
            ]);
        }
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        $rules = [
            'title' => 'required|string|max:255',
            'excerpt' => 'required|string|max:255',
            'content' => 'required|string'
        ];

        if (request()->isMethod('post')) {
            $rules['featured_image'] = 'required|image|mimes:jpeg,png,jpg';
            $rules['tags'] = 'required';
        } else {
            $rules['featured_image'] = 'image|mimes:jpeg,png,jpg';
        }

        return $rules;
    }
}

PostFactory

<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Post;
use App\User;
use Faker\Generator as Faker;

$factory->define(Post::class, function (Faker $faker) {
    return [
        'author_id' => factory(User::class),
        'title' => $faker->sentence,
        'excerpt' => $faker->sentence,
        'content' => $faker->paragraph,
        'featured_image' => 'images/0aa186eb49f844476d7e2e9448f69496.jpg',
        'published_at' => null,
        'tags' => '[{"id":7,"name":"Makayla Wiegand IV"}]'
    ];
});

TestCase

public function testAuthorizedUserCanCreatePost()
    {
        $this->withoutExceptionHandling();
        $this->signIn();

        $post = make('Post');

        $response = $this->post("/posts", $post->toArray());
        // dd($response->headers->get('Location'));
        $this->assertDatabaseHas('posts', ['title' => $post->title]);
        // $response->assertRedirect($response->headers->get('Location'));
        $this->get($response->headers->get('Location'))
            ->assertSee($post->title);
    }

As you see, i has post tags and featured_image in creating blog. Validate them in post request. When i run test, it show the given data was invalid with withoutExceptionHandling in test case. I am confusing how to make test pass and my code is really messy. Please give me advice.

0 likes
6 replies
aleahy's avatar

Your post is failing one of the validation checks.

Try getting the response json so you can see what the error is.

dd($response->getContent());

You'll need to remove withoutExceptionHandling to see the response.

AungHtetPaing__'s avatar

@aleahy i got too many line of response.

When i run first time, i got this

Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity constraint violation: 19 FOREIGN KEY constraint failed (SQL: insert into &quot;post_tag&quot; (&quot;post_id&quot;, &quot;tag_id&quot;) values (1, 7)) in file /Users/aunghtetpaing/code/hielo/vendor/laravel/framework/src/Illuminate/Database/Connection.php on line 671\n

And then i removed tags column in post factory, i got this

ErrorException: foreach() argument must be of type array|object, null given in file /Users/aunghtetpaing/code/hielo/app/Http/Controllers/PostsController.php on line 67\n

The problem is tags column and featured_image, i think so. I added tags fake string in the post factory. I don't know what to do next. It is failing.

AungHtetPaing__'s avatar

Right now test is passing.

public function testAuthorizedUserCanCreatePost()
    {
        $this->withoutExceptionHandling();
        $this->signIn();

        $image = UploadedFile::fake()->image('test_image.jpg');

        $post = make('Post', ['featured_image' => $image]);
        $tag = create('Tag', ['id' => 7, 'name' => 'Makayla Wiegand IV']);

        $this->post("/posts", $post->toArray() + ['tags' => '[{"id":7,"name":"Makayla Wiegand IV"}]'])
            ->assertStatus(200);

        $this->assertDatabaseHas('posts', ['title' => $post->title]);

        $post = Post::first();

        $this->get("/posts/$post->id")
            ->assertSee($post->title);
    }

Removed tags column in post factory and changed validation rule.

$rules['featured_image.*'] = 'required|image|mimetypes:image/jpeg,image/png';
$rules['tags'] = 'required';
martinbean's avatar
Level 80

@aunghtetpaing__ I’m not reading through that line of text, but will say when I’m testing endpoints that deal with validation, I’ll use ->assertSessionHasNoErrors() on web requests and ->assertJsonMissingValidationErrors() on API requests.

If validation does fail when I’m not expecting it to, the rules that failed and their messages will be printed to the console in a nice manner so that I can debug the issue without having to temporarily disable exception handling or stick dd statements in my tests.

1 like

Please or to participate in this conversation.