Python for the .NET developer Transcripts
Lecture: Mocking our dependencies with pytest_mock
0:00 Well we made a good shot at it. We tried to write
0:03 some test where we set the style as electric.
0:06 And we call all guitars and we just do this
0:08 cool little assert statement to check that
0:10 Hey we only got electric guitars back.
0:12 That worked but as you recall
0:15 we were actually touching the two dependencies
0:18 that we were not suppose to be touching.
0:20 The Pytest-fixtures
0:22 come to the rescue again here.
0:23 So what we're going to do
0:25 is up at the top, is we're going to.
0:26 Let me put it like this. import pytest-mock.
0:32 Remember that's one of the dependencies we added
0:35 in addition to Pytest itself. So if we have pytest-mock
0:39 we can come down here and we can say
0:40 that we have a mocker.
0:42 This is another test fixture
0:44 but instead of us defining it
0:45 Pytest-mock does it. So let's just print out what this is.
0:51 And it's a little bit hard to see
0:52 with the formatting.
0:53 But we have a Pytest plugin mock fixture right there.
0:56 And we can help us ourselves a lot
0:59 if we put some type annotations in this.
1:00 So this is a list of guitar.
1:09 And this one is a pytest-mock.mocker
1:13 mock fixture. Here we go.
1:15 Those are the two we're using.
1:16 And so far it says
1:17 you're not using this but you know what
1:18 we're about to.
1:19 So we need to do two things here.
1:22 We need to mock out or patch
1:25 in terms of Pythons terminology
1:27 the two dependencies.
1:29 So with .NET what you do is
1:31 you actually pass interface objects in.
1:33 And instead of passing them real production ones
1:35 you pass test one. Python is very easy to reach down
1:39 and redefine what a function does.
1:41 And C# had some crazy post-compilation mechanisms
1:46 to do this as well back in Team Foundation
1:49 way back in the day. I don't think they
1:51 really push that much anymore. But the idea is
1:53 we're going to reach down
1:54 and basically forward this function
1:57 this test function here. We're going to redefine
2:01 what a couple of the functions are.
2:02 So here's how it goes. We say mocker.patch
2:07 Sigh. Here's *args, **kwargs. Again
2:10 I hate this. While you're working
2:12 it tells you nothing about
2:13 what we have to do.
2:14 But what you have to do is you have to put
2:15 the full name. So module-name.function-name.
2:21 Now let's go down here and have a quick look.
2:24 Hit guitars from db. So this is the function
2:29 that we're working with. We can say autospec.
2:33 If anything here gets called its fine.
2:36 But what we actually want to specify
2:37 is return value is this guitar data.
2:42 That's our other test fixture
2:44 where our test data's coming in.
2:46 We already saw that that's great.
2:48 Let's see if this will run at all.
2:52 Well that's a good sign.
2:53 What's an even better sign
2:54 is notice here. Our hey
2:56 we're going to the database.
2:57 That should never happen. That's gone.
3:01 Why's that gone? That's awesome!
3:02 So let's go down here.
3:04 And when we call all_guitars
3:06 a couple of things are happening.
3:08 We're logging it
3:09 that's the message that still is there.
3:10 And we haven't replaced that dependency.
3:12 But this used to be a simulated database call.
3:15 But now it's just a mock.
3:17 And all it does is say hey
3:19 if you call me, you'll get that test data
3:20 that we defined over in the other.
3:23 No not that one. Our test fixtures.
3:25 Here you get this test data
3:26 that we're returning from our test fixture.
3:29 Okay so we call this one
3:31 no longer going to the database
3:33 as it were. Actually here. call this one
3:38 we're no longer going to the database.
3:39 Because for the scope of this function
3:42 Pytest-mock has redefined what that function means.
3:45 And it'll put it back when it's done.
3:47 The other job we got to deal with here
3:49 is we have to do this also. For lib.log
3:54 we don't care about any return value.
3:56 We just need it to not get in the way.
3:58 Let's run this again. Boom! There it is!
4:03 It's passed and you know, if we dig into this
4:04 we go look at the output there's no output.
4:07 Alright just to show you
4:08 if I put this back and run it again.
4:11 There's our logging patch out again
4:14 no more logging. Because we're no longer
4:16 going to the database or going to the log.
4:19 We replace what those two dependencies mean.
4:21 Now you might think oh we better put this
4:23 into a try. Finally where we rollback the patch
4:26 or into a with statement
4:29 Pythons equivalent of a using statement.
4:32 Normally that would be true
4:33 but the way this mocker works
4:35 is it's already implemented that.
4:37 There's some clever stuff happening
4:39 in the implementation
4:40 for the test fixtures. So just assume this is totally safe to do
4:44 even if we have some kind of crash.
4:46 Alright, cool, right!
4:47 So we've finally got this working.
4:48 And now lets do one other test.
4:50 Maybe it's a little silly
4:52 lets just say test all_guitars.
4:54 And the style is going to be all like that.
4:59 And we no longer can do this cool thing.
5:03 Instead, we're going to have types.
5:06 do a set comprehension.
5:08 So a little inline expression
5:10 that generates a set. And this will just be
5:12 g.style for g in guitars. Like that.
5:18 And then our assert will be that
5:21 types is equal to the set
5:24 with acoustic and electric.
5:29 Let's run that and make sure everything is working.
5:32 Perfect! It passes. Just to verify we're
5:35 actually testing something
5:36 let's see what happens if we weren't quite right.
5:39 Problem is we expected acoustic
5:41 and we got what we're really looking for.
5:43 So perfect. It looks like this
5:46 is testing what we're looking to test.
5:47 The final thing is these two are good here.
5:50 These are great
5:51 but we're only testing the happy path.
5:54 In a moment we're going to test
5:56 the not so happy path. To make sure that
5:58 even when we pass bad data
6:00 maybe it doesn't succeed.
6:01 But it fails in a predictable and expected way.