FrazeColder's avatar

PHPUnit how to write a Laravel Nova Observer test

Hi,

I would like to write a test for my CommentObserver. This observer is only registered in the NovaServiceProvider but not the AppServiceProvider. This means I cannot test my observer by using my own Controllers.

In my eyes I have 3 ways to test my observer:

  1. Either performing a feature test by sending a post request to the Nova API
  2. Mocking the observer by calling the function in the observer to check if the function perfoms as desired
  3. Trying to register my observer on the fly in the AppServiceProvider, performing a request and deregistering the observer in the AppServiceProvider again.

I tried to find a solution for any of these 3 ways to test my observer but unfortunately I faild with any of them.

Problems:

  • For way 1 I always get a validation error and Nova tells me that my input is invalid.
  • For way 2 I fail at mocking the observer function (when mocking the observer I also need to fake an HTTP request)
  • For way 3 I didn't find any solution on how to register and deregister the oberserver on the fly at the AppServiceProvider

Do you guys have idea and solition on how I can test my CommentObserver (which is as written above only registered in my NovaServiceProvider).

Update: So, here is the code of my observer. I need to have an valid request to test my observer in order to have the ability to access the $request->input('images') variable. I do know I can also use $comment->content instead of request()->input('content') because $comment->content already contains the new content which is not saved it this point.

The reason why I need a valid request is that the variable images is not part of the Comment model. So I cannot use $comment->images because it simply doesn't exist. That's why I need to access the request input. What my observer is basically doing is to extract the base64 images from the content, saves them to the server and replaces them by an image link.

class CommentObserver
{
    public function updating(Comment $comment)
    {
        if (!request()->input('content')) {
            return;
        }

        if (request()->input('content') == $comment->getRawOriginal('content')) {
            return;
        }

        $images = request()->input('images');
        if(!is_array($images)) {
            $images = json_decode(request()->input('images'));
        }

        checkExistingImagesAndDeleteWhenNotFound($comment, request()->input('content'), 'comments', 'medium');
        $comment->content = addBase64ImagesToModelFromContent($comment, request()->input('content'), $images, 'comments', 'medium');
    }
}

This is my test so far. I choose way 1 but as described already this always leads to an validation error by the nova controller and I cannot figure out what is the error/what is missing or wrong.

class CommentObserverTest extends TestCase
{
    /** @test */
    public function it_test()
    {
        $user = User::factory()->create([
            'role_id' => Role::getIdByName('admin')
        ]);

        $product = Product::factory()->create();

        $comment = Comment::factory()->create(['user_id' => $user->id, 'content' => '<p>Das ist wirklich ein super Preis!</p>', 'commentable_type' => 'App\Models\Product', 'commentable_id' => $product->id]);

        $data = [
            'content' => '<p>Das ist wirklich ein HAMMER Preis!</p>',
            'contentDraftId' => '278350e2-1b6b-4009-b4a5-05b92aedaae6',
            'pageStatus' => PageStatus::getIdByStatus('publish'),
            'pageStatus_trashed' => false,
            'commentable' => $product->id,
            'commentable_type' => 'App\Models\Product',
            'commentable_trashed' => false,
            'user' => $user->id,
            'user_trashed' => false,
            '_method' => 'PUT',
            '_retrieved_at' => now()
        ];

        $this->actingAs($user);

        $response = $this->put('http://nova.mywebsiteproject.test/nova-api/comments/' . $comment->id, $data);

        dd($response->decodeResponseJson());

        $das = new CommentObserver();
    }
}

Kind regards and thank you

0 likes
15 replies
bugsysha's avatar

I usually test them in the simplest possible form.

$post = Post::factory()->make();

(new PostObserver)->creating($post);

$this->assertSame('slug', $post->slug);

FrazeColder's avatar

That is a very nice solition but I have one problem. I have to access a variable which is being send in the $request variable... So I have to either fake the request or I have to perform a Laravel Nova feature test.

Any idea in how I can do this?

bugsysha's avatar

I never fake anything regarding core framework features (request, session, etc...). There is always a way to do it. So if you present the code I'm sure there is a way to do it.

FrazeColder's avatar

I have now updated the question above. I would be very thankful if you take a look :)

bugsysha's avatar

I would advise that you rewrite that logic since the place it is located in right now (observer) is not the right place for that kind of logic. Consider creating an orchestrating class that will handle the logic or maybe even place it in some existing piece of code where you can pass the data you need.

FrazeColder's avatar

The problem is, this code should only be executed when updating a Comment model in Laravel Nova. How else can I achive this? I think the only way is using an Observer which is only registered in Laravel Nova.

bugsysha's avatar

I guess I would use actions. When it is a normal update then they use the traditional Nova UI. But since it looks like something specific, I would use Actions. If the normal update never happens, then I would forbid users the option to do the normal update and that would force them to use specific actions for what they want to achieve. That way you will have more control and will avoid situations where you have to check if you have content or other required info.

FrazeColder's avatar

Well… I cannot use actions. The reason why is I need to intercept the way Laravel Nova is updating the model.

I want the user to be able to update the content normally in Nova. Now I need to intercept the normal update process in order to extract the base64 images and replace them with img links. That’s nothing you can perform with an action.

bugsysha's avatar

The reason why is I need to intercept the way Laravel Nova is updating the model.

That is more like how you are going to solve the problem and not why.

I want the user to be able to update the content normally in Nova. Now I need to intercept the normal update process in order to extract the base64 images and replace them with img links. That’s nothing you can perform with an action.

Sure you can do it with actions. Maybe you just need to wrap your mind around it a bit to see how. If you need help feel free to ask here. I think with the approach you are taking in the moment you are making your life harder.

FrazeColder's avatar

Hey @bugsysha

I have now took a look into the Nova documentation and this is what actions a doing:

Nova actions allow you to perform custom tasks on one or more Eloquent models. For example, you might write an action that sends an email to a user containing account data they have requested. Or, you might write an action to transfer a group of records to another user.

Well, I don't know how you are imaging to write a action which updates the content of a comment model. Actions are e.g. to delete the comment or to set the page status of the comment to private. But how should a user be able to edit the content of an comment with an action?

Can you please explain me how you would do that?

bugsysha's avatar

Sure. First I would like to clarify that I don't fully understand what you are trying to do. But based on what I've seen I would say that you can achieve what you need with actions.

So, when you select an action and you click the "play" button to run it, you can set as many question dialogs as you like. When action runs after all the questions were answered you will have direct access to those answers. So you would probably want to have multiple actions, for which you will have specific dedicated questions to achieve a specific goal. Think of them like when you create console commands.

If this doesn't answer your question, can you be more specific with what you want me to answer?

FrazeColder's avatar

Alright, gotcha. Well yes, this is a way you can go but I would like to have one place where I can edit all my fields and this is the edit button in Nova. This button is designated to do exactly what I want. Doing it via actions would be a massive workaround.

bugsysha's avatar

I guess all of that depends on your viewpoint. From my perspective what you are doing is covering specific scenarios and for that, I would rather have an obvious well named (descriptive) action. I don't like having my models depend on anything that is not related to the model itself. That proved to be a pain point in many cases before and I would do everything to avoid it.

Wells263's avatar

There tend to be not many people who can certainly write not so simple posts that artistically.

1 like

Please or to participate in this conversation.